Skip to content

Commit d11ceba

Browse files
lafriksAbdulrhmnGhanem
authored andcommitted
Add support for rendering terminal output with colors (go-gitea#19497)
1 parent faf34a1 commit d11ceba

File tree

10 files changed

+466
-0
lines changed

10 files changed

+466
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/PuerkitoBio/goquery v1.8.0
1616
github.com/alecthomas/chroma v0.10.0
1717
github.com/blevesearch/bleve/v2 v2.3.2
18+
github.com/buildkite/terminal-to-html/v3 v3.6.1
1819
github.com/caddyserver/certmagic v0.16.1
1920
github.com/chi-middleware/proxy v1.1.1
2021
github.com/denisenkom/go-mssqldb v0.12.0

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
265265
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
266266
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
267267
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
268+
github.com/buildkite/terminal-to-html/v3 v3.6.1 h1:yHS+GXsPDXevb67YXjkVwZ4tolDCgPYa9RVOrzHlgGE=
269+
github.com/buildkite/terminal-to-html/v3 v3.6.1/go.mod h1:g0ME1XqbkBSgXR9YmlIHcJIjzaMyWW+HbsG0rPb5puo=
268270
github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
269271
github.com/caddyserver/certmagic v0.16.1 h1:rdSnjcUVJojmL4M0efJ+yHXErrrijS4YYg3FuwRdJkI=
270272
github.com/caddyserver/certmagic v0.16.1/go.mod h1:jKQ5n+ViHAr6DbPwEGLTSM2vDwTO6EvCKBblBRUvvuQ=
@@ -1499,6 +1501,7 @@ github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
14991501
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
15001502
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
15011503
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
1504+
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
15021505
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
15031506
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
15041507
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"code.gitea.io/gitea/modules/setting"
1919

2020
// register supported doc types
21+
_ "code.gitea.io/gitea/modules/markup/console"
2122
_ "code.gitea.io/gitea/modules/markup/csv"
2223
_ "code.gitea.io/gitea/modules/markup/markdown"
2324
_ "code.gitea.io/gitea/modules/markup/orgmode"

modules/markup/console/console.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package console
6+
7+
import (
8+
"bytes"
9+
"io"
10+
"path/filepath"
11+
"regexp"
12+
"strings"
13+
14+
"code.gitea.io/gitea/modules/markup"
15+
"code.gitea.io/gitea/modules/setting"
16+
17+
trend "github.com/buildkite/terminal-to-html/v3"
18+
"github.com/go-enry/go-enry/v2"
19+
)
20+
21+
// MarkupName describes markup's name
22+
var MarkupName = "console"
23+
24+
func init() {
25+
markup.RegisterRenderer(Renderer{})
26+
}
27+
28+
// Renderer implements markup.Renderer
29+
type Renderer struct{}
30+
31+
// Name implements markup.Renderer
32+
func (Renderer) Name() string {
33+
return MarkupName
34+
}
35+
36+
// NeedPostProcess implements markup.Renderer
37+
func (Renderer) NeedPostProcess() bool { return false }
38+
39+
// Extensions implements markup.Renderer
40+
func (Renderer) Extensions() []string {
41+
return []string{".sh-session"}
42+
}
43+
44+
// SanitizerRules implements markup.Renderer
45+
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
46+
return []setting.MarkupSanitizerRule{
47+
{Element: "span", AllowAttr: "class", Regexp: regexp.MustCompile(`^term-((fg[ix]?|bg)\d+|container)$`)},
48+
}
49+
}
50+
51+
// SanitizerDisabled disabled sanitize if return true
52+
func (Renderer) SanitizerDisabled() bool {
53+
return false
54+
}
55+
56+
// CanRender implements markup.RendererContentDetector
57+
func (Renderer) CanRender(filename string, input io.Reader) bool {
58+
buf, err := io.ReadAll(input)
59+
if err != nil {
60+
return false
61+
}
62+
if enry.GetLanguage(filepath.Base(filename), buf) != enry.OtherLanguage {
63+
return false
64+
}
65+
return bytes.ContainsRune(buf, '\x1b')
66+
}
67+
68+
// Render renders terminal colors to HTML with all specific handling stuff.
69+
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
70+
buf, err := io.ReadAll(input)
71+
if err != nil {
72+
return err
73+
}
74+
buf = trend.Render(buf)
75+
buf = bytes.ReplaceAll(buf, []byte("\n"), []byte(`<br>`))
76+
_, err = output.Write(buf)
77+
return err
78+
}
79+
80+
// Render renders terminal colors to HTML with all specific handling stuff.
81+
func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
82+
if ctx.Type == "" {
83+
ctx.Type = MarkupName
84+
}
85+
return markup.Render(ctx, input, output)
86+
}
87+
88+
// RenderString renders terminal colors in string to HTML with all specific handling stuff and return string
89+
func RenderString(ctx *markup.RenderContext, content string) (string, error) {
90+
var buf strings.Builder
91+
if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
92+
return "", err
93+
}
94+
return buf.String(), nil
95+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package console
6+
7+
import (
8+
"strings"
9+
"testing"
10+
11+
"code.gitea.io/gitea/modules/markup"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestRenderConsole(t *testing.T) {
17+
var render Renderer
18+
kases := map[string]string{
19+
"\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",
20+
}
21+
22+
for k, v := range kases {
23+
var buf strings.Builder
24+
canRender := render.CanRender("test", strings.NewReader(k))
25+
assert.True(t, canRender)
26+
27+
err := render.Render(&markup.RenderContext{}, strings.NewReader(k), &buf)
28+
assert.NoError(t, err)
29+
assert.EqualValues(t, v, buf.String())
30+
}
31+
}

modules/markup/renderer.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package markup
66

77
import (
8+
"bytes"
89
"context"
910
"errors"
1011
"fmt"
@@ -93,6 +94,12 @@ type Renderer interface {
9394
Render(ctx *RenderContext, input io.Reader, output io.Writer) error
9495
}
9596

97+
// RendererContentDetector detects if the content can be rendered
98+
// by specified renderer
99+
type RendererContentDetector interface {
100+
CanRender(filename string, input io.Reader) bool
101+
}
102+
96103
var (
97104
extRenderers = make(map[string]Renderer)
98105
renderers = make(map[string]Renderer)
@@ -117,6 +124,20 @@ func GetRendererByType(tp string) Renderer {
117124
return renderers[tp]
118125
}
119126

127+
// DetectRendererType detects the markup type of the content
128+
func DetectRendererType(filename string, input io.Reader) string {
129+
buf, err := io.ReadAll(input)
130+
if err != nil {
131+
return ""
132+
}
133+
for _, renderer := range renderers {
134+
if detector, ok := renderer.(RendererContentDetector); ok && detector.CanRender(filename, bytes.NewReader(buf)) {
135+
return renderer.Name()
136+
}
137+
}
138+
return ""
139+
}
140+
120141
// Render renders markup file to HTML with all specific handling stuff.
121142
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
122143
if ctx.Type != "" {

routers/web/repo/view.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
509509
ctx.Data["ReadmeExist"] = readmeExist
510510

511511
markupType := markup.Type(blob.Name())
512+
// If the markup is detected by custom markup renderer it should not be reset later on
513+
// to not pass it down to the render context.
514+
detected := false
515+
if markupType == "" {
516+
detected = true
517+
markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
518+
}
512519
if markupType != "" {
513520
ctx.Data["HasSourceRenderedToggle"] = true
514521
}
@@ -517,8 +524,12 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
517524
ctx.Data["IsMarkup"] = true
518525
ctx.Data["MarkupType"] = markupType
519526
var result strings.Builder
527+
if !detected {
528+
markupType = ""
529+
}
520530
err := markup.Render(&markup.RenderContext{
521531
Ctx: ctx,
532+
Type: markupType,
522533
Filename: blob.Name(),
523534
URLPrefix: path.Dir(treeLink),
524535
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),

web_src/less/_base.less

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
--color-secondary-alpha-70: #dededeb3;
5959
--color-secondary-alpha-80: #dededecc;
6060
--color-secondary-alpha-90: #dededee1;
61+
/* console colors */
62+
--color-console-fg: #ffffff;
63+
--color-console-bg: #171717;
6164
/* colors */
6265
--color-red: #db2828;
6366
--color-orange: #f2711c;

0 commit comments

Comments
 (0)