mirror of
https://gitea.com/gitea/tea.git
synced 2026-01-12 01:52:07 +01:00
Use glamour and termev to render/colorize content (#181)
Merge branch 'master' into use-glamour select Glamour Theme based on BackgroundColor Merge branch 'master' into use-glamour Merge branch 'master' into use-glamour update termev update go.mod label color colorate use glamour for issue content Vendor: Add glamour Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/181 Reviewed-by: techknowlogick <techknowlogick@gitea.io> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
104
vendor/github.com/charmbracelet/glamour/ansi/baseelement.go
generated
vendored
Normal file
104
vendor/github.com/charmbracelet/glamour/ansi/baseelement.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"text/template"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
// BaseElement renders a styled primitive element.
|
||||
type BaseElement struct {
|
||||
Token string
|
||||
Prefix string
|
||||
Suffix string
|
||||
Style StylePrimitive
|
||||
}
|
||||
|
||||
func formatToken(format string, token string) (string, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
v := make(map[string]interface{})
|
||||
v["text"] = token
|
||||
|
||||
tmpl, err := template.New(format).Funcs(TemplateFuncMap).Parse(format)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = tmpl.Execute(&b, v)
|
||||
return b.String(), err
|
||||
}
|
||||
|
||||
func renderText(w io.Writer, p termenv.Profile, rules StylePrimitive, s string) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out := termenv.String(s)
|
||||
|
||||
if rules.Color != nil {
|
||||
out = out.Foreground(p.Color(*rules.Color))
|
||||
}
|
||||
if rules.BackgroundColor != nil {
|
||||
out = out.Background(p.Color(*rules.BackgroundColor))
|
||||
}
|
||||
|
||||
if rules.Underline != nil && *rules.Underline {
|
||||
out = out.Underline()
|
||||
}
|
||||
if rules.Bold != nil && *rules.Bold {
|
||||
out = out.Bold()
|
||||
}
|
||||
if rules.Italic != nil && *rules.Italic {
|
||||
out = out.Italic()
|
||||
}
|
||||
if rules.CrossedOut != nil && *rules.CrossedOut {
|
||||
out = out.CrossOut()
|
||||
}
|
||||
if rules.Overlined != nil && *rules.Overlined {
|
||||
out = out.Overline()
|
||||
}
|
||||
if rules.Inverse != nil && *rules.Inverse {
|
||||
out = out.Reverse()
|
||||
}
|
||||
if rules.Blink != nil && *rules.Blink {
|
||||
out = out.Blink()
|
||||
}
|
||||
|
||||
_, _ = w.Write([]byte(out.String()))
|
||||
}
|
||||
|
||||
func (e *BaseElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Prefix)
|
||||
defer func() {
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Suffix)
|
||||
}()
|
||||
|
||||
rules := bs.With(e.Style)
|
||||
// render unstyled prefix/suffix
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
|
||||
defer func() {
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
|
||||
}()
|
||||
|
||||
// render styled prefix/suffix
|
||||
renderText(w, ctx.options.ColorProfile, rules, rules.Prefix)
|
||||
defer func() {
|
||||
renderText(w, ctx.options.ColorProfile, rules, rules.Suffix)
|
||||
}()
|
||||
|
||||
s := e.Token
|
||||
if len(rules.Format) > 0 {
|
||||
var err error
|
||||
s, err = formatToken(rules.Format, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
renderText(w, ctx.options.ColorProfile, rules, s)
|
||||
return nil
|
||||
}
|
||||
59
vendor/github.com/charmbracelet/glamour/ansi/blockelement.go
generated
vendored
Normal file
59
vendor/github.com/charmbracelet/glamour/ansi/blockelement.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/muesli/reflow/wordwrap"
|
||||
)
|
||||
|
||||
// BlockElement provides a render buffer for children of a block element.
|
||||
// After all children have been rendered into it, it applies indentation and
|
||||
// margins around them and writes everything to the parent rendering buffer.
|
||||
type BlockElement struct {
|
||||
Block *bytes.Buffer
|
||||
Style StyleBlock
|
||||
Margin bool
|
||||
Newline bool
|
||||
}
|
||||
|
||||
func (e *BlockElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
bs.Push(*e)
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, e.Style.BlockPrefix)
|
||||
renderText(bs.Current().Block, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Style.Prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *BlockElement) Finish(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
|
||||
if e.Margin {
|
||||
mw := NewMarginWriter(ctx, w, bs.Current().Style)
|
||||
_, err := mw.Write(
|
||||
wordwrap.Bytes(bs.Current().Block.Bytes(), int(bs.Width(ctx))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.Newline {
|
||||
_, err = mw.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err := bs.Parent().Block.Write(bs.Current().Block.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Style.Suffix)
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, e.Style.BlockSuffix)
|
||||
|
||||
bs.Current().Block.Reset()
|
||||
bs.Pop()
|
||||
return nil
|
||||
}
|
||||
95
vendor/github.com/charmbracelet/glamour/ansi/blockstack.go
generated
vendored
Normal file
95
vendor/github.com/charmbracelet/glamour/ansi/blockstack.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// BlockStack is a stack of block elements, used to calculate the current
|
||||
// indentation & margin level during the rendering process.
|
||||
type BlockStack []BlockElement
|
||||
|
||||
// Len returns the length of the stack.
|
||||
func (s *BlockStack) Len() int {
|
||||
return len(*s)
|
||||
}
|
||||
|
||||
// Push appends an item to the stack.
|
||||
func (s *BlockStack) Push(e BlockElement) {
|
||||
*s = append(*s, e)
|
||||
}
|
||||
|
||||
// Pop removes the last item on the stack.
|
||||
func (s *BlockStack) Pop() {
|
||||
stack := *s
|
||||
if len(stack) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
stack = stack[0 : len(stack)-1]
|
||||
*s = stack
|
||||
}
|
||||
|
||||
// Indent returns the current indentation level of all elements in the stack.
|
||||
func (s BlockStack) Indent() uint {
|
||||
var i uint
|
||||
|
||||
for _, v := range s {
|
||||
if v.Style.Indent == nil {
|
||||
continue
|
||||
}
|
||||
i += *v.Style.Indent
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// Margin returns the current margin level of all elements in the stack.
|
||||
func (s BlockStack) Margin() uint {
|
||||
var i uint
|
||||
|
||||
for _, v := range s {
|
||||
if v.Style.Margin == nil {
|
||||
continue
|
||||
}
|
||||
i += *v.Style.Margin
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// Width returns the available rendering width
|
||||
func (s BlockStack) Width(ctx RenderContext) uint {
|
||||
if s.Indent()+s.Margin()*2 > uint(ctx.options.WordWrap) {
|
||||
return 0
|
||||
}
|
||||
return uint(ctx.options.WordWrap) - s.Indent() - s.Margin()*2
|
||||
}
|
||||
|
||||
// Parent returns the current BlockElement's parent.
|
||||
func (s BlockStack) Parent() BlockElement {
|
||||
if len(s) == 1 {
|
||||
return BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
}
|
||||
}
|
||||
|
||||
return s[len(s)-2]
|
||||
}
|
||||
|
||||
// Current returns the current BlockElement.
|
||||
func (s BlockStack) Current() BlockElement {
|
||||
if len(s) == 0 {
|
||||
return BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
}
|
||||
}
|
||||
|
||||
return s[len(s)-1]
|
||||
}
|
||||
|
||||
// With returns a StylePrimitive that inherits the current BlockElement's style.
|
||||
func (s BlockStack) With(child StylePrimitive) StylePrimitive {
|
||||
sb := StyleBlock{}
|
||||
sb.StylePrimitive = child
|
||||
return cascadeStyle(s.Current().Style, sb, false).StylePrimitive
|
||||
}
|
||||
125
vendor/github.com/charmbracelet/glamour/ansi/codeblock.go
generated
vendored
Normal file
125
vendor/github.com/charmbracelet/glamour/ansi/codeblock.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/quick"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/muesli/reflow/indent"
|
||||
)
|
||||
|
||||
// A CodeBlockElement is used to render code blocks.
|
||||
type CodeBlockElement struct {
|
||||
Code string
|
||||
Language string
|
||||
}
|
||||
|
||||
func chromaStyle(style StylePrimitive) string {
|
||||
var s string
|
||||
|
||||
if style.Color != nil {
|
||||
s = *style.Color
|
||||
}
|
||||
if style.BackgroundColor != nil {
|
||||
if s != "" {
|
||||
s += " "
|
||||
}
|
||||
s += "bg:" + *style.BackgroundColor
|
||||
}
|
||||
if style.Italic != nil && *style.Italic {
|
||||
if s != "" {
|
||||
s += " "
|
||||
}
|
||||
s += "italic"
|
||||
}
|
||||
if style.Bold != nil && *style.Bold {
|
||||
if s != "" {
|
||||
s += " "
|
||||
}
|
||||
s += "bold"
|
||||
}
|
||||
if style.Underline != nil && *style.Underline {
|
||||
if s != "" {
|
||||
s += " "
|
||||
}
|
||||
s += "underline"
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
|
||||
var indentation uint
|
||||
var margin uint
|
||||
rules := ctx.options.Styles.CodeBlock
|
||||
if rules.Indent != nil {
|
||||
indentation = *rules.Indent
|
||||
}
|
||||
if rules.Margin != nil {
|
||||
margin = *rules.Margin
|
||||
}
|
||||
theme := rules.Theme
|
||||
|
||||
if rules.Chroma != nil && ctx.options.ColorProfile > 1 {
|
||||
theme = "charm"
|
||||
styles.Register(chroma.MustNewStyle("charm",
|
||||
chroma.StyleEntries{
|
||||
chroma.Text: chromaStyle(rules.Chroma.Text),
|
||||
chroma.Error: chromaStyle(rules.Chroma.Error),
|
||||
chroma.Comment: chromaStyle(rules.Chroma.Comment),
|
||||
chroma.CommentPreproc: chromaStyle(rules.Chroma.CommentPreproc),
|
||||
chroma.Keyword: chromaStyle(rules.Chroma.Keyword),
|
||||
chroma.KeywordReserved: chromaStyle(rules.Chroma.KeywordReserved),
|
||||
chroma.KeywordNamespace: chromaStyle(rules.Chroma.KeywordNamespace),
|
||||
chroma.KeywordType: chromaStyle(rules.Chroma.KeywordType),
|
||||
chroma.Operator: chromaStyle(rules.Chroma.Operator),
|
||||
chroma.Punctuation: chromaStyle(rules.Chroma.Punctuation),
|
||||
chroma.Name: chromaStyle(rules.Chroma.Name),
|
||||
chroma.NameBuiltin: chromaStyle(rules.Chroma.NameBuiltin),
|
||||
chroma.NameTag: chromaStyle(rules.Chroma.NameTag),
|
||||
chroma.NameAttribute: chromaStyle(rules.Chroma.NameAttribute),
|
||||
chroma.NameClass: chromaStyle(rules.Chroma.NameClass),
|
||||
chroma.NameConstant: chromaStyle(rules.Chroma.NameConstant),
|
||||
chroma.NameDecorator: chromaStyle(rules.Chroma.NameDecorator),
|
||||
chroma.NameException: chromaStyle(rules.Chroma.NameException),
|
||||
chroma.NameFunction: chromaStyle(rules.Chroma.NameFunction),
|
||||
chroma.NameOther: chromaStyle(rules.Chroma.NameOther),
|
||||
chroma.Literal: chromaStyle(rules.Chroma.Literal),
|
||||
chroma.LiteralNumber: chromaStyle(rules.Chroma.LiteralNumber),
|
||||
chroma.LiteralDate: chromaStyle(rules.Chroma.LiteralDate),
|
||||
chroma.LiteralString: chromaStyle(rules.Chroma.LiteralString),
|
||||
chroma.LiteralStringEscape: chromaStyle(rules.Chroma.LiteralStringEscape),
|
||||
chroma.GenericDeleted: chromaStyle(rules.Chroma.GenericDeleted),
|
||||
chroma.GenericEmph: chromaStyle(rules.Chroma.GenericEmph),
|
||||
chroma.GenericInserted: chromaStyle(rules.Chroma.GenericInserted),
|
||||
chroma.GenericStrong: chromaStyle(rules.Chroma.GenericStrong),
|
||||
chroma.GenericSubheading: chromaStyle(rules.Chroma.GenericSubheading),
|
||||
chroma.Background: chromaStyle(rules.Chroma.Background),
|
||||
}))
|
||||
}
|
||||
|
||||
iw := indent.NewWriterPipe(w, indentation+margin, func(wr io.Writer) {
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, " ")
|
||||
})
|
||||
|
||||
if len(theme) > 0 {
|
||||
renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
|
||||
err := quick.Highlight(iw, e.Code, e.Language, "terminal256", theme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
|
||||
return nil
|
||||
}
|
||||
|
||||
// fallback rendering
|
||||
el := &BaseElement{
|
||||
Token: e.Code,
|
||||
Style: rules.StylePrimitive,
|
||||
}
|
||||
|
||||
return el.Render(iw, ctx)
|
||||
}
|
||||
38
vendor/github.com/charmbracelet/glamour/ansi/context.go
generated
vendored
Normal file
38
vendor/github.com/charmbracelet/glamour/ansi/context.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"html"
|
||||
"strings"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
)
|
||||
|
||||
// RenderContext holds the current rendering options and state.
|
||||
type RenderContext struct {
|
||||
options Options
|
||||
|
||||
blockStack *BlockStack
|
||||
table *TableElement
|
||||
|
||||
stripper *bluemonday.Policy
|
||||
}
|
||||
|
||||
// NewRenderContext returns a new RenderContext.
|
||||
func NewRenderContext(options Options) RenderContext {
|
||||
return RenderContext{
|
||||
options: options,
|
||||
blockStack: &BlockStack{},
|
||||
table: &TableElement{},
|
||||
stripper: bluemonday.StrictPolicy(),
|
||||
}
|
||||
}
|
||||
|
||||
// SanitizeHTML sanitizes HTML content.
|
||||
func (ctx RenderContext) SanitizeHTML(s string, trimSpaces bool) string {
|
||||
s = ctx.stripper.Sanitize(s)
|
||||
if trimSpaces {
|
||||
s = strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
return html.UnescapeString(s)
|
||||
}
|
||||
401
vendor/github.com/charmbracelet/glamour/ansi/elements.go
generated
vendored
Normal file
401
vendor/github.com/charmbracelet/glamour/ansi/elements.go
generated
vendored
Normal file
@@ -0,0 +1,401 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
astext "github.com/yuin/goldmark/extension/ast"
|
||||
)
|
||||
|
||||
// ElementRenderer is called when entering a markdown node.
|
||||
type ElementRenderer interface {
|
||||
Render(w io.Writer, ctx RenderContext) error
|
||||
}
|
||||
|
||||
// ElementFinisher is called when leaving a markdown node.
|
||||
type ElementFinisher interface {
|
||||
Finish(w io.Writer, ctx RenderContext) error
|
||||
}
|
||||
|
||||
// An Element is used to instruct the renderer how to handle individual markdown
|
||||
// nodes.
|
||||
type Element struct {
|
||||
Entering string
|
||||
Exiting string
|
||||
Renderer ElementRenderer
|
||||
Finisher ElementFinisher
|
||||
}
|
||||
|
||||
// NewElement returns the appropriate render Element for a given node.
|
||||
func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
|
||||
ctx := tr.context
|
||||
// fmt.Print(strings.Repeat(" ", ctx.blockStack.Len()), node.Type(), node.Kind())
|
||||
// defer fmt.Println()
|
||||
|
||||
switch node.Kind() {
|
||||
// Document
|
||||
case ast.KindDocument:
|
||||
e := &BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
Style: ctx.options.Styles.Document,
|
||||
Margin: true,
|
||||
}
|
||||
return Element{
|
||||
Renderer: e,
|
||||
Finisher: e,
|
||||
}
|
||||
|
||||
// Heading
|
||||
case ast.KindHeading:
|
||||
n := node.(*ast.Heading)
|
||||
he := &HeadingElement{
|
||||
Level: n.Level,
|
||||
First: node.PreviousSibling() == nil,
|
||||
}
|
||||
return Element{
|
||||
Exiting: "",
|
||||
Renderer: he,
|
||||
Finisher: he,
|
||||
}
|
||||
|
||||
// Paragraph
|
||||
case ast.KindParagraph:
|
||||
if node.Parent() != nil && node.Parent().Kind() == ast.KindListItem {
|
||||
return Element{}
|
||||
}
|
||||
return Element{
|
||||
Renderer: &ParagraphElement{
|
||||
First: node.PreviousSibling() == nil,
|
||||
},
|
||||
Finisher: &ParagraphElement{},
|
||||
}
|
||||
|
||||
// Blockquote
|
||||
case ast.KindBlockquote:
|
||||
e := &BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.BlockQuote, false),
|
||||
Margin: true,
|
||||
Newline: true,
|
||||
}
|
||||
return Element{
|
||||
Entering: "\n",
|
||||
Renderer: e,
|
||||
Finisher: e,
|
||||
}
|
||||
|
||||
// Lists
|
||||
case ast.KindList:
|
||||
s := ctx.options.Styles.List.StyleBlock
|
||||
if s.Indent == nil {
|
||||
var i uint
|
||||
s.Indent = &i
|
||||
}
|
||||
n := node.Parent()
|
||||
for n != nil {
|
||||
if n.Kind() == ast.KindList {
|
||||
i := ctx.options.Styles.List.LevelIndent
|
||||
s.Indent = &i
|
||||
break
|
||||
}
|
||||
n = n.Parent()
|
||||
}
|
||||
|
||||
e := &BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
Style: cascadeStyle(ctx.blockStack.Current().Style, s, false),
|
||||
Margin: true,
|
||||
Newline: true,
|
||||
}
|
||||
return Element{
|
||||
Entering: "\n",
|
||||
Renderer: e,
|
||||
Finisher: e,
|
||||
}
|
||||
|
||||
case ast.KindListItem:
|
||||
var l uint
|
||||
var e uint
|
||||
l = 1
|
||||
n := node
|
||||
for n.PreviousSibling() != nil && (n.PreviousSibling().Kind() == ast.KindListItem) {
|
||||
l++
|
||||
n = n.PreviousSibling()
|
||||
}
|
||||
if node.Parent().(*ast.List).IsOrdered() {
|
||||
e = l
|
||||
}
|
||||
|
||||
post := "\n"
|
||||
if (node.LastChild() != nil && node.LastChild().Kind() == ast.KindList) ||
|
||||
node.NextSibling() == nil {
|
||||
post = ""
|
||||
}
|
||||
|
||||
if node.FirstChild() != nil &&
|
||||
node.FirstChild().FirstChild() != nil &&
|
||||
node.FirstChild().FirstChild().Kind() == astext.KindTaskCheckBox {
|
||||
nc := node.FirstChild().FirstChild().(*astext.TaskCheckBox)
|
||||
|
||||
return Element{
|
||||
Exiting: post,
|
||||
Renderer: &TaskElement{
|
||||
Checked: nc.IsChecked,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return Element{
|
||||
Exiting: post,
|
||||
Renderer: &ItemElement{
|
||||
Enumeration: e,
|
||||
},
|
||||
}
|
||||
|
||||
// Text Elements
|
||||
case ast.KindText:
|
||||
n := node.(*ast.Text)
|
||||
s := string(n.Segment.Value(source))
|
||||
|
||||
if n.HardLineBreak() || (n.SoftLineBreak()) {
|
||||
s += "\n"
|
||||
}
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Token: html.UnescapeString(s),
|
||||
Style: ctx.options.Styles.Text,
|
||||
},
|
||||
}
|
||||
|
||||
case ast.KindEmphasis:
|
||||
n := node.(*ast.Emphasis)
|
||||
s := string(n.Text(source))
|
||||
style := ctx.options.Styles.Emph
|
||||
if n.Level > 1 {
|
||||
style = ctx.options.Styles.Strong
|
||||
}
|
||||
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Token: html.UnescapeString(s),
|
||||
Style: style,
|
||||
},
|
||||
}
|
||||
|
||||
case astext.KindStrikethrough:
|
||||
n := node.(*astext.Strikethrough)
|
||||
s := string(n.Text(source))
|
||||
style := ctx.options.Styles.Strikethrough
|
||||
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Token: html.UnescapeString(s),
|
||||
Style: style,
|
||||
},
|
||||
}
|
||||
|
||||
case ast.KindThematicBreak:
|
||||
return Element{
|
||||
Entering: "",
|
||||
Exiting: "",
|
||||
Renderer: &BaseElement{
|
||||
Style: ctx.options.Styles.HorizontalRule,
|
||||
},
|
||||
}
|
||||
|
||||
// Links
|
||||
case ast.KindLink:
|
||||
n := node.(*ast.Link)
|
||||
return Element{
|
||||
Renderer: &LinkElement{
|
||||
Text: textFromChildren(node, source),
|
||||
BaseURL: ctx.options.BaseURL,
|
||||
URL: string(n.Destination),
|
||||
},
|
||||
}
|
||||
case ast.KindAutoLink:
|
||||
n := node.(*ast.AutoLink)
|
||||
u := string(n.URL(source))
|
||||
label := string(n.Label(source))
|
||||
if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(u), "mailto:") {
|
||||
u = "mailto:" + u
|
||||
}
|
||||
|
||||
return Element{
|
||||
Renderer: &LinkElement{
|
||||
Text: label,
|
||||
BaseURL: ctx.options.BaseURL,
|
||||
URL: u,
|
||||
},
|
||||
}
|
||||
|
||||
// Images
|
||||
case ast.KindImage:
|
||||
n := node.(*ast.Image)
|
||||
text := string(n.Text(source))
|
||||
return Element{
|
||||
Renderer: &ImageElement{
|
||||
Text: text,
|
||||
BaseURL: ctx.options.BaseURL,
|
||||
URL: string(n.Destination),
|
||||
},
|
||||
}
|
||||
|
||||
// Code
|
||||
case ast.KindFencedCodeBlock:
|
||||
n := node.(*ast.FencedCodeBlock)
|
||||
l := n.Lines().Len()
|
||||
s := ""
|
||||
for i := 0; i < l; i++ {
|
||||
line := n.Lines().At(i)
|
||||
s += string(line.Value(source))
|
||||
}
|
||||
return Element{
|
||||
Entering: "\n",
|
||||
Renderer: &CodeBlockElement{
|
||||
Code: s,
|
||||
Language: string(n.Language(source)),
|
||||
},
|
||||
}
|
||||
|
||||
case ast.KindCodeBlock:
|
||||
n := node.(*ast.CodeBlock)
|
||||
l := n.Lines().Len()
|
||||
s := ""
|
||||
for i := 0; i < l; i++ {
|
||||
line := n.Lines().At(i)
|
||||
s += string(line.Value(source))
|
||||
}
|
||||
return Element{
|
||||
Entering: "\n",
|
||||
Renderer: &CodeBlockElement{
|
||||
Code: s,
|
||||
},
|
||||
}
|
||||
|
||||
case ast.KindCodeSpan:
|
||||
// n := node.(*ast.CodeSpan)
|
||||
e := &BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.Code, false),
|
||||
}
|
||||
return Element{
|
||||
Renderer: e,
|
||||
Finisher: e,
|
||||
}
|
||||
|
||||
// Tables
|
||||
case astext.KindTable:
|
||||
te := &TableElement{}
|
||||
return Element{
|
||||
Entering: "\n",
|
||||
Renderer: te,
|
||||
Finisher: te,
|
||||
}
|
||||
|
||||
case astext.KindTableCell:
|
||||
s := ""
|
||||
n := node.FirstChild()
|
||||
for n != nil {
|
||||
s += string(n.Text(source))
|
||||
// s += string(n.LinkData.Destination)
|
||||
n = n.NextSibling()
|
||||
}
|
||||
|
||||
return Element{
|
||||
Renderer: &TableCellElement{
|
||||
Text: s,
|
||||
Head: node.Parent().Kind() == astext.KindTableHeader,
|
||||
},
|
||||
}
|
||||
|
||||
case astext.KindTableHeader:
|
||||
return Element{
|
||||
Finisher: &TableHeadElement{},
|
||||
}
|
||||
case astext.KindTableRow:
|
||||
return Element{
|
||||
Finisher: &TableRowElement{},
|
||||
}
|
||||
|
||||
// HTML Elements
|
||||
case ast.KindHTMLBlock:
|
||||
n := node.(*ast.HTMLBlock)
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Token: ctx.SanitizeHTML(string(n.Text(source)), true) + "\n",
|
||||
Style: ctx.options.Styles.HTMLBlock.StylePrimitive,
|
||||
},
|
||||
}
|
||||
case ast.KindRawHTML:
|
||||
n := node.(*ast.RawHTML)
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Token: ctx.SanitizeHTML(string(n.Text(source)), true),
|
||||
Style: ctx.options.Styles.HTMLSpan.StylePrimitive,
|
||||
},
|
||||
}
|
||||
|
||||
// Definition Lists
|
||||
case astext.KindDefinitionList:
|
||||
e := &BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.DefinitionList, false),
|
||||
Margin: true,
|
||||
Newline: true,
|
||||
}
|
||||
return Element{
|
||||
Entering: "\n",
|
||||
Renderer: e,
|
||||
Finisher: e,
|
||||
}
|
||||
|
||||
case astext.KindDefinitionTerm:
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Style: ctx.options.Styles.DefinitionTerm,
|
||||
},
|
||||
}
|
||||
|
||||
case astext.KindDefinitionDescription:
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Style: ctx.options.Styles.DefinitionDescription,
|
||||
},
|
||||
}
|
||||
|
||||
// Handled by parents
|
||||
case astext.KindTaskCheckBox:
|
||||
// handled by KindListItem
|
||||
return Element{}
|
||||
case ast.KindTextBlock:
|
||||
return Element{}
|
||||
|
||||
// Unknown case
|
||||
default:
|
||||
fmt.Println("Warning: unhandled element", node.Kind().String())
|
||||
return Element{}
|
||||
}
|
||||
}
|
||||
|
||||
func textFromChildren(node ast.Node, source []byte) string {
|
||||
var s string
|
||||
for c := node.FirstChild(); c != nil; c = c.NextSibling() {
|
||||
if c.Kind() == ast.KindText {
|
||||
cn := c.(*ast.Text)
|
||||
s += string(cn.Segment.Value(source))
|
||||
|
||||
if cn.HardLineBreak() || (cn.SoftLineBreak()) {
|
||||
s += "\n"
|
||||
}
|
||||
} else {
|
||||
s += string(c.Text(source))
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
86
vendor/github.com/charmbracelet/glamour/ansi/heading.go
generated
vendored
Normal file
86
vendor/github.com/charmbracelet/glamour/ansi/heading.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/muesli/reflow/indent"
|
||||
"github.com/muesli/reflow/wordwrap"
|
||||
)
|
||||
|
||||
// A HeadingElement is used to render headings.
|
||||
type HeadingElement struct {
|
||||
Level int
|
||||
First bool
|
||||
}
|
||||
|
||||
func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
rules := ctx.options.Styles.Heading
|
||||
|
||||
switch e.Level {
|
||||
case 1:
|
||||
rules = cascadeStyles(true, rules, ctx.options.Styles.H1)
|
||||
case 2:
|
||||
rules = cascadeStyles(true, rules, ctx.options.Styles.H2)
|
||||
case 3:
|
||||
rules = cascadeStyles(true, rules, ctx.options.Styles.H3)
|
||||
case 4:
|
||||
rules = cascadeStyles(true, rules, ctx.options.Styles.H4)
|
||||
case 5:
|
||||
rules = cascadeStyles(true, rules, ctx.options.Styles.H5)
|
||||
case 6:
|
||||
rules = cascadeStyles(true, rules, ctx.options.Styles.H6)
|
||||
}
|
||||
|
||||
if !e.First {
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, "\n")
|
||||
}
|
||||
|
||||
be := BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
Style: cascadeStyle(bs.Current().Style, rules, false),
|
||||
}
|
||||
bs.Push(be)
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockPrefix)
|
||||
renderText(bs.Current().Block, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *HeadingElement) Finish(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
rules := bs.Current().Style
|
||||
|
||||
var indentation uint
|
||||
var margin uint
|
||||
if rules.Indent != nil {
|
||||
indentation = *rules.Indent
|
||||
}
|
||||
if rules.Margin != nil {
|
||||
margin = *rules.Margin
|
||||
}
|
||||
|
||||
iw := indent.NewWriterPipe(w, indentation+margin, func(wr io.Writer) {
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, " ")
|
||||
})
|
||||
|
||||
flow := wordwrap.NewWriter(int(bs.Width(ctx) - indentation - margin*2))
|
||||
_, err := flow.Write(bs.Current().Block.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flow.Close()
|
||||
|
||||
_, err = iw.Write(flow.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Suffix)
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockSuffix)
|
||||
|
||||
bs.Current().Block.Reset()
|
||||
bs.Pop()
|
||||
return nil
|
||||
}
|
||||
39
vendor/github.com/charmbracelet/glamour/ansi/image.go
generated
vendored
Normal file
39
vendor/github.com/charmbracelet/glamour/ansi/image.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// An ImageElement is used to render images elements.
|
||||
type ImageElement struct {
|
||||
Text string
|
||||
BaseURL string
|
||||
URL string
|
||||
Child ElementRenderer // FIXME
|
||||
}
|
||||
|
||||
func (e *ImageElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
if len(e.Text) > 0 {
|
||||
el := &BaseElement{
|
||||
Token: e.Text,
|
||||
Style: ctx.options.Styles.ImageText,
|
||||
}
|
||||
err := el.Render(w, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(e.URL) > 0 {
|
||||
el := &BaseElement{
|
||||
Token: resolveRelativeURL(e.BaseURL, e.URL),
|
||||
Prefix: " ",
|
||||
Style: ctx.options.Styles.Image,
|
||||
}
|
||||
err := el.Render(w, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
78
vendor/github.com/charmbracelet/glamour/ansi/link.go
generated
vendored
Normal file
78
vendor/github.com/charmbracelet/glamour/ansi/link.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// A LinkElement is used to render hyperlinks.
|
||||
type LinkElement struct {
|
||||
Text string
|
||||
BaseURL string
|
||||
URL string
|
||||
Child ElementRenderer // FIXME
|
||||
}
|
||||
|
||||
func (e *LinkElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
var textRendered bool
|
||||
if len(e.Text) > 0 && e.Text != e.URL {
|
||||
textRendered = true
|
||||
|
||||
el := &BaseElement{
|
||||
Token: e.Text,
|
||||
Style: ctx.options.Styles.LinkText,
|
||||
}
|
||||
err := el.Render(w, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if node.LastChild != nil {
|
||||
if node.LastChild.Type == bf.Image {
|
||||
el := tr.NewElement(node.LastChild)
|
||||
err := el.Renderer.Render(w, node.LastChild, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(node.LastChild.Literal) > 0 &&
|
||||
string(node.LastChild.Literal) != string(node.LinkData.Destination) {
|
||||
textRendered = true
|
||||
el := &BaseElement{
|
||||
Token: string(node.LastChild.Literal),
|
||||
Style: ctx.style[LinkText],
|
||||
}
|
||||
err := el.Render(w, node.LastChild, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
u, err := url.Parse(e.URL)
|
||||
if err == nil &&
|
||||
"#"+u.Fragment != e.URL { // if the URL only consists of an anchor, ignore it
|
||||
pre := " "
|
||||
style := ctx.options.Styles.Link
|
||||
if !textRendered {
|
||||
pre = ""
|
||||
style.BlockPrefix = ""
|
||||
style.BlockSuffix = ""
|
||||
}
|
||||
|
||||
el := &BaseElement{
|
||||
Token: resolveRelativeURL(e.BaseURL, e.URL),
|
||||
Prefix: pre,
|
||||
Style: style,
|
||||
}
|
||||
err := el.Render(w, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
27
vendor/github.com/charmbracelet/glamour/ansi/listitem.go
generated
vendored
Normal file
27
vendor/github.com/charmbracelet/glamour/ansi/listitem.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// An ItemElement is used to render items inside a list.
|
||||
type ItemElement struct {
|
||||
Enumeration uint
|
||||
}
|
||||
|
||||
func (e *ItemElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
var el *BaseElement
|
||||
if e.Enumeration > 0 {
|
||||
el = &BaseElement{
|
||||
Style: ctx.options.Styles.Enumeration,
|
||||
Prefix: strconv.FormatInt(int64(e.Enumeration), 10),
|
||||
}
|
||||
} else {
|
||||
el = &BaseElement{
|
||||
Style: ctx.options.Styles.Item,
|
||||
}
|
||||
}
|
||||
|
||||
return el.Render(w, ctx)
|
||||
}
|
||||
52
vendor/github.com/charmbracelet/glamour/ansi/margin.go
generated
vendored
Normal file
52
vendor/github.com/charmbracelet/glamour/ansi/margin.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/muesli/reflow/indent"
|
||||
"github.com/muesli/reflow/padding"
|
||||
)
|
||||
|
||||
// MarginWriter is a Writer that applies indentation and padding around
|
||||
// whatever you write to it.
|
||||
type MarginWriter struct {
|
||||
w io.Writer
|
||||
pw *padding.Writer
|
||||
iw *indent.Writer
|
||||
}
|
||||
|
||||
// NewMarginWriter returns a new MarginWriter.
|
||||
func NewMarginWriter(ctx RenderContext, w io.Writer, rules StyleBlock) *MarginWriter {
|
||||
bs := ctx.blockStack
|
||||
|
||||
var indentation uint
|
||||
var margin uint
|
||||
if rules.Indent != nil {
|
||||
indentation = *rules.Indent
|
||||
}
|
||||
if rules.Margin != nil {
|
||||
margin = *rules.Margin
|
||||
}
|
||||
|
||||
pw := padding.NewWriterPipe(w, bs.Width(ctx), func(wr io.Writer) {
|
||||
renderText(w, ctx.options.ColorProfile, rules.StylePrimitive, " ")
|
||||
})
|
||||
|
||||
ic := " "
|
||||
if rules.IndentToken != nil {
|
||||
ic = *rules.IndentToken
|
||||
}
|
||||
iw := indent.NewWriterPipe(pw, indentation+margin, func(wr io.Writer) {
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, ic)
|
||||
})
|
||||
|
||||
return &MarginWriter{
|
||||
w: w,
|
||||
pw: pw,
|
||||
iw: iw,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *MarginWriter) Write(b []byte) (int, error) {
|
||||
return w.iw.Write(b)
|
||||
}
|
||||
58
vendor/github.com/charmbracelet/glamour/ansi/paragraph.go
generated
vendored
Normal file
58
vendor/github.com/charmbracelet/glamour/ansi/paragraph.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/muesli/reflow/wordwrap"
|
||||
)
|
||||
|
||||
// A ParagraphElement is used to render individual paragraphs.
|
||||
type ParagraphElement struct {
|
||||
First bool
|
||||
}
|
||||
|
||||
func (e *ParagraphElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
rules := ctx.options.Styles.Paragraph
|
||||
|
||||
if !e.First {
|
||||
_, _ = w.Write([]byte("\n"))
|
||||
}
|
||||
be := BlockElement{
|
||||
Block: &bytes.Buffer{},
|
||||
Style: cascadeStyle(bs.Current().Style, rules, false),
|
||||
}
|
||||
bs.Push(be)
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockPrefix)
|
||||
renderText(bs.Current().Block, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
rules := bs.Current().Style
|
||||
|
||||
mw := NewMarginWriter(ctx, w, rules)
|
||||
if len(strings.TrimSpace(bs.Current().Block.String())) > 0 {
|
||||
flow := wordwrap.NewWriter(int(bs.Width(ctx)))
|
||||
flow.KeepNewlines = false
|
||||
_, _ = flow.Write(bs.Current().Block.Bytes())
|
||||
flow.Close()
|
||||
|
||||
_, err := mw.Write(flow.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = mw.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Suffix)
|
||||
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockSuffix)
|
||||
|
||||
bs.Current().Block.Reset()
|
||||
bs.Pop()
|
||||
return nil
|
||||
}
|
||||
163
vendor/github.com/charmbracelet/glamour/ansi/renderer.go
generated
vendored
Normal file
163
vendor/github.com/charmbracelet/glamour/ansi/renderer.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
astext "github.com/yuin/goldmark/extension/ast"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Options is used to configure an ANSIRenderer.
|
||||
type Options struct {
|
||||
BaseURL string
|
||||
WordWrap int
|
||||
ColorProfile termenv.Profile
|
||||
Styles StyleConfig
|
||||
}
|
||||
|
||||
// ANSIRenderer renders markdown content as ANSI escaped sequences.
|
||||
type ANSIRenderer struct {
|
||||
context RenderContext
|
||||
}
|
||||
|
||||
// NewRenderer returns a new ANSIRenderer with style and options set.
|
||||
func NewRenderer(options Options) *ANSIRenderer {
|
||||
return &ANSIRenderer{
|
||||
context: NewRenderContext(options),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterFuncs implements NodeRenderer.RegisterFuncs.
|
||||
func (r *ANSIRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
// blocks
|
||||
reg.Register(ast.KindDocument, r.renderNode)
|
||||
reg.Register(ast.KindHeading, r.renderNode)
|
||||
reg.Register(ast.KindBlockquote, r.renderNode)
|
||||
reg.Register(ast.KindCodeBlock, r.renderNode)
|
||||
reg.Register(ast.KindFencedCodeBlock, r.renderNode)
|
||||
reg.Register(ast.KindHTMLBlock, r.renderNode)
|
||||
reg.Register(ast.KindList, r.renderNode)
|
||||
reg.Register(ast.KindListItem, r.renderNode)
|
||||
reg.Register(ast.KindParagraph, r.renderNode)
|
||||
reg.Register(ast.KindTextBlock, r.renderNode)
|
||||
reg.Register(ast.KindThematicBreak, r.renderNode)
|
||||
|
||||
// inlines
|
||||
reg.Register(ast.KindAutoLink, r.renderNode)
|
||||
reg.Register(ast.KindCodeSpan, r.renderNode)
|
||||
reg.Register(ast.KindEmphasis, r.renderNode)
|
||||
reg.Register(ast.KindImage, r.renderNode)
|
||||
reg.Register(ast.KindLink, r.renderNode)
|
||||
reg.Register(ast.KindRawHTML, r.renderNode)
|
||||
reg.Register(ast.KindText, r.renderNode)
|
||||
reg.Register(ast.KindString, r.renderNode)
|
||||
|
||||
// tables
|
||||
reg.Register(astext.KindTable, r.renderNode)
|
||||
reg.Register(astext.KindTableHeader, r.renderNode)
|
||||
reg.Register(astext.KindTableRow, r.renderNode)
|
||||
reg.Register(astext.KindTableCell, r.renderNode)
|
||||
|
||||
// definitions
|
||||
reg.Register(astext.KindDefinitionList, r.renderNode)
|
||||
reg.Register(astext.KindDefinitionTerm, r.renderNode)
|
||||
reg.Register(astext.KindDefinitionDescription, r.renderNode)
|
||||
|
||||
// footnotes
|
||||
reg.Register(astext.KindFootnote, r.renderNode)
|
||||
reg.Register(astext.KindFootnoteList, r.renderNode)
|
||||
reg.Register(astext.KindFootnoteLink, r.renderNode)
|
||||
reg.Register(astext.KindFootnoteBackLink, r.renderNode)
|
||||
|
||||
// checkboxes
|
||||
reg.Register(astext.KindTaskCheckBox, r.renderNode)
|
||||
|
||||
// strikethrough
|
||||
reg.Register(astext.KindStrikethrough, r.renderNode)
|
||||
}
|
||||
|
||||
func (r *ANSIRenderer) renderNode(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
// _, _ = w.Write([]byte(node.Type.String()))
|
||||
writeTo := io.Writer(w)
|
||||
bs := r.context.blockStack
|
||||
|
||||
// children get rendered by their parent
|
||||
if isChild(node) {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
e := r.NewElement(node, source)
|
||||
if entering {
|
||||
// everything below the Document element gets rendered into a block buffer
|
||||
if bs.Len() > 0 {
|
||||
writeTo = io.Writer(bs.Current().Block)
|
||||
}
|
||||
|
||||
_, _ = writeTo.Write([]byte(e.Entering))
|
||||
if e.Renderer != nil {
|
||||
err := e.Renderer.Render(writeTo, r.context)
|
||||
if err != nil {
|
||||
return ast.WalkStop, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// everything below the Document element gets rendered into a block buffer
|
||||
if bs.Len() > 0 {
|
||||
writeTo = io.Writer(bs.Parent().Block)
|
||||
}
|
||||
|
||||
// if we're finished rendering the entire document,
|
||||
// flush to the real writer
|
||||
if node.Type() == ast.TypeDocument {
|
||||
writeTo = w
|
||||
}
|
||||
|
||||
if e.Finisher != nil {
|
||||
err := e.Finisher.Finish(writeTo, r.context)
|
||||
if err != nil {
|
||||
return ast.WalkStop, err
|
||||
}
|
||||
}
|
||||
_, _ = bs.Current().Block.Write([]byte(e.Exiting))
|
||||
}
|
||||
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func isChild(node ast.Node) bool {
|
||||
if node.Parent() != nil && node.Parent().Kind() == ast.KindBlockquote {
|
||||
// skip paragraph within blockquote to avoid reflowing text
|
||||
return true
|
||||
}
|
||||
for n := node.Parent(); n != nil; n = n.Parent() {
|
||||
// These types are already rendered by their parent
|
||||
switch n.Kind() {
|
||||
case ast.KindLink, ast.KindImage, ast.KindEmphasis, astext.KindStrikethrough, astext.KindTableCell:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func resolveRelativeURL(baseURL string, rel string) string {
|
||||
u, err := url.Parse(rel)
|
||||
if err != nil {
|
||||
return rel
|
||||
}
|
||||
if u.IsAbs() {
|
||||
return rel
|
||||
}
|
||||
u.Path = strings.TrimPrefix(u.Path, "/")
|
||||
|
||||
base, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return rel
|
||||
}
|
||||
return base.ResolveReference(u).String()
|
||||
}
|
||||
227
vendor/github.com/charmbracelet/glamour/ansi/style.go
generated
vendored
Normal file
227
vendor/github.com/charmbracelet/glamour/ansi/style.go
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
package ansi
|
||||
|
||||
// Chroma holds all the chroma settings.
|
||||
type Chroma struct {
|
||||
Text StylePrimitive `json:"text,omitempty"`
|
||||
Error StylePrimitive `json:"error,omitempty"`
|
||||
Comment StylePrimitive `json:"comment,omitempty"`
|
||||
CommentPreproc StylePrimitive `json:"comment_preproc,omitempty"`
|
||||
Keyword StylePrimitive `json:"keyword,omitempty"`
|
||||
KeywordReserved StylePrimitive `json:"keyword_reserved,omitempty"`
|
||||
KeywordNamespace StylePrimitive `json:"keyword_namespace,omitempty"`
|
||||
KeywordType StylePrimitive `json:"keyword_type,omitempty"`
|
||||
Operator StylePrimitive `json:"operator,omitempty"`
|
||||
Punctuation StylePrimitive `json:"punctuation,omitempty"`
|
||||
Name StylePrimitive `json:"name,omitempty"`
|
||||
NameBuiltin StylePrimitive `json:"name_builtin,omitempty"`
|
||||
NameTag StylePrimitive `json:"name_tag,omitempty"`
|
||||
NameAttribute StylePrimitive `json:"name_attribute,omitempty"`
|
||||
NameClass StylePrimitive `json:"name_class,omitempty"`
|
||||
NameConstant StylePrimitive `json:"name_constant,omitempty"`
|
||||
NameDecorator StylePrimitive `json:"name_decorator,omitempty"`
|
||||
NameException StylePrimitive `json:"name_exception,omitempty"`
|
||||
NameFunction StylePrimitive `json:"name_function,omitempty"`
|
||||
NameOther StylePrimitive `json:"name_other,omitempty"`
|
||||
Literal StylePrimitive `json:"literal,omitempty"`
|
||||
LiteralNumber StylePrimitive `json:"literal_number,omitempty"`
|
||||
LiteralDate StylePrimitive `json:"literal_date,omitempty"`
|
||||
LiteralString StylePrimitive `json:"literal_string,omitempty"`
|
||||
LiteralStringEscape StylePrimitive `json:"literal_string_escape,omitempty"`
|
||||
GenericDeleted StylePrimitive `json:"generic_deleted,omitempty"`
|
||||
GenericEmph StylePrimitive `json:"generic_emph,omitempty"`
|
||||
GenericInserted StylePrimitive `json:"generic_inserted,omitempty"`
|
||||
GenericStrong StylePrimitive `json:"generic_strong,omitempty"`
|
||||
GenericSubheading StylePrimitive `json:"generic_subheading,omitempty"`
|
||||
Background StylePrimitive `json:"background,omitempty"`
|
||||
}
|
||||
|
||||
// StylePrimitive holds all the basic style settings.
|
||||
type StylePrimitive struct {
|
||||
BlockPrefix string `json:"block_prefix,omitempty"`
|
||||
BlockSuffix string `json:"block_suffix,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty"`
|
||||
Color *string `json:"color,omitempty"`
|
||||
BackgroundColor *string `json:"background_color,omitempty"`
|
||||
Underline *bool `json:"underline,omitempty"`
|
||||
Bold *bool `json:"bold,omitempty"`
|
||||
Italic *bool `json:"italic,omitempty"`
|
||||
CrossedOut *bool `json:"crossed_out,omitempty"`
|
||||
Faint *bool `json:"faint,omitempty"`
|
||||
Conceal *bool `json:"conceal,omitempty"`
|
||||
Overlined *bool `json:"overlined,omitempty"`
|
||||
Inverse *bool `json:"inverse,omitempty"`
|
||||
Blink *bool `json:"blink,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
// StyleTask holds the style settings for a task item.
|
||||
type StyleTask struct {
|
||||
StylePrimitive
|
||||
Ticked string `json:"ticked,omitempty"`
|
||||
Unticked string `json:"unticked,omitempty"`
|
||||
}
|
||||
|
||||
// StyleBlock holds the basic style settings for block elements.
|
||||
type StyleBlock struct {
|
||||
StylePrimitive
|
||||
Indent *uint `json:"indent,omitempty"`
|
||||
IndentToken *string `json:"indent_token,omitempty"`
|
||||
Margin *uint `json:"margin,omitempty"`
|
||||
}
|
||||
|
||||
// StyleCodeBlock holds the style settings for a code block.
|
||||
type StyleCodeBlock struct {
|
||||
StyleBlock
|
||||
Theme string `json:"theme,omitempty"`
|
||||
Chroma *Chroma `json:"chroma,omitempty"`
|
||||
}
|
||||
|
||||
// StyleList holds the style settings for a list.
|
||||
type StyleList struct {
|
||||
StyleBlock
|
||||
LevelIndent uint `json:"level_indent,omitempty"`
|
||||
}
|
||||
|
||||
// StyleTable holds the style settings for a table.
|
||||
type StyleTable struct {
|
||||
StyleBlock
|
||||
CenterSeparator *string `json:"center_separator,omitempty"`
|
||||
ColumnSeparator *string `json:"column_separator,omitempty"`
|
||||
RowSeparator *string `json:"row_separator,omitempty"`
|
||||
}
|
||||
|
||||
// StyleConfig is used to configure the styling behavior of an ANSIRenderer.
|
||||
type StyleConfig struct {
|
||||
Document StyleBlock `json:"document,omitempty"`
|
||||
BlockQuote StyleBlock `json:"block_quote,omitempty"`
|
||||
Paragraph StyleBlock `json:"paragraph,omitempty"`
|
||||
List StyleList `json:"list,omitempty"`
|
||||
|
||||
Heading StyleBlock `json:"heading,omitempty"`
|
||||
H1 StyleBlock `json:"h1,omitempty"`
|
||||
H2 StyleBlock `json:"h2,omitempty"`
|
||||
H3 StyleBlock `json:"h3,omitempty"`
|
||||
H4 StyleBlock `json:"h4,omitempty"`
|
||||
H5 StyleBlock `json:"h5,omitempty"`
|
||||
H6 StyleBlock `json:"h6,omitempty"`
|
||||
|
||||
Text StylePrimitive `json:"text,omitempty"`
|
||||
Strikethrough StylePrimitive `json:"strikethrough,omitempty"`
|
||||
Emph StylePrimitive `json:"emph,omitempty"`
|
||||
Strong StylePrimitive `json:"strong,omitempty"`
|
||||
HorizontalRule StylePrimitive `json:"hr,omitempty"`
|
||||
|
||||
Item StylePrimitive `json:"item,omitempty"`
|
||||
Enumeration StylePrimitive `json:"enumeration,omitempty"`
|
||||
Task StyleTask `json:"task,omitempty"`
|
||||
|
||||
Link StylePrimitive `json:"link,omitempty"`
|
||||
LinkText StylePrimitive `json:"link_text,omitempty"`
|
||||
|
||||
Image StylePrimitive `json:"image,omitempty"`
|
||||
ImageText StylePrimitive `json:"image_text,omitempty"`
|
||||
|
||||
Code StyleBlock `json:"code,omitempty"`
|
||||
CodeBlock StyleCodeBlock `json:"code_block,omitempty"`
|
||||
|
||||
Table StyleTable `json:"table,omitempty"`
|
||||
|
||||
DefinitionList StyleBlock `json:"definition_list,omitempty"`
|
||||
DefinitionTerm StylePrimitive `json:"definition_term,omitempty"`
|
||||
DefinitionDescription StylePrimitive `json:"definition_description,omitempty"`
|
||||
|
||||
HTMLBlock StyleBlock `json:"html_block,omitempty"`
|
||||
HTMLSpan StyleBlock `json:"html_span,omitempty"`
|
||||
}
|
||||
|
||||
func cascadeStyles(toBlock bool, s ...StyleBlock) StyleBlock {
|
||||
var r StyleBlock
|
||||
|
||||
for _, v := range s {
|
||||
r = cascadeStyle(r, v, toBlock)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func cascadeStyle(parent StyleBlock, child StyleBlock, toBlock bool) StyleBlock {
|
||||
s := child
|
||||
|
||||
s.Color = parent.Color
|
||||
s.BackgroundColor = parent.BackgroundColor
|
||||
s.Underline = parent.Underline
|
||||
s.Bold = parent.Bold
|
||||
s.Italic = parent.Italic
|
||||
s.CrossedOut = parent.CrossedOut
|
||||
s.Faint = parent.Faint
|
||||
s.Conceal = parent.Conceal
|
||||
s.Overlined = parent.Overlined
|
||||
s.Inverse = parent.Inverse
|
||||
s.Blink = parent.Blink
|
||||
|
||||
if toBlock {
|
||||
s.Indent = parent.Indent
|
||||
s.Margin = parent.Margin
|
||||
s.BlockPrefix = parent.BlockPrefix
|
||||
s.BlockSuffix = parent.BlockSuffix
|
||||
s.Prefix = parent.Prefix
|
||||
s.Suffix = parent.Suffix
|
||||
}
|
||||
|
||||
if child.Color != nil {
|
||||
s.Color = child.Color
|
||||
}
|
||||
if child.BackgroundColor != nil {
|
||||
s.BackgroundColor = child.BackgroundColor
|
||||
}
|
||||
if child.Indent != nil {
|
||||
s.Indent = child.Indent
|
||||
}
|
||||
if child.Margin != nil {
|
||||
s.Margin = child.Margin
|
||||
}
|
||||
if child.Underline != nil {
|
||||
s.Underline = child.Underline
|
||||
}
|
||||
if child.Bold != nil {
|
||||
s.Bold = child.Bold
|
||||
}
|
||||
if child.Italic != nil {
|
||||
s.Italic = child.Italic
|
||||
}
|
||||
if child.CrossedOut != nil {
|
||||
s.CrossedOut = child.CrossedOut
|
||||
}
|
||||
if child.Faint != nil {
|
||||
s.Faint = child.Faint
|
||||
}
|
||||
if child.Conceal != nil {
|
||||
s.Conceal = child.Conceal
|
||||
}
|
||||
if child.Overlined != nil {
|
||||
s.Overlined = child.Overlined
|
||||
}
|
||||
if child.Inverse != nil {
|
||||
s.Inverse = child.Inverse
|
||||
}
|
||||
if child.Blink != nil {
|
||||
s.Blink = child.Blink
|
||||
}
|
||||
if child.BlockPrefix != "" {
|
||||
s.BlockPrefix = child.BlockPrefix
|
||||
}
|
||||
if child.BlockSuffix != "" {
|
||||
s.BlockSuffix = child.BlockSuffix
|
||||
}
|
||||
if child.Prefix != "" {
|
||||
s.Prefix = child.Prefix
|
||||
}
|
||||
if child.Suffix != "" {
|
||||
s.Suffix = child.Suffix
|
||||
}
|
||||
if child.Format != "" {
|
||||
s.Format = child.Format
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
33
vendor/github.com/charmbracelet/glamour/ansi/stylewriter.go
generated
vendored
Normal file
33
vendor/github.com/charmbracelet/glamour/ansi/stylewriter.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// StyleWriter is a Writer that applies styling on whatever you write to it.
|
||||
type StyleWriter struct {
|
||||
ctx RenderContext
|
||||
w io.Writer
|
||||
buf bytes.Buffer
|
||||
rules StylePrimitive
|
||||
}
|
||||
|
||||
// NewStyleWriter returns a new StyleWriter.
|
||||
func NewStyleWriter(ctx RenderContext, w io.Writer, rules StylePrimitive) *StyleWriter {
|
||||
return &StyleWriter{
|
||||
ctx: ctx,
|
||||
w: w,
|
||||
rules: rules,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *StyleWriter) Write(b []byte) (int, error) {
|
||||
return w.buf.Write(b)
|
||||
}
|
||||
|
||||
// Close must be called when you're finished writing to a StyleWriter.
|
||||
func (w *StyleWriter) Close() error {
|
||||
renderText(w.w, w.ctx.options.ColorProfile, w.rules, w.buf.String())
|
||||
return nil
|
||||
}
|
||||
108
vendor/github.com/charmbracelet/glamour/ansi/table.go
generated
vendored
Normal file
108
vendor/github.com/charmbracelet/glamour/ansi/table.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/muesli/reflow/indent"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// A TableElement is used to render tables.
|
||||
type TableElement struct {
|
||||
writer *tablewriter.Table
|
||||
styleWriter *StyleWriter
|
||||
header []string
|
||||
cell []string
|
||||
}
|
||||
|
||||
// A TableRowElement is used to render a single row in a table.
|
||||
type TableRowElement struct {
|
||||
}
|
||||
|
||||
// A TableHeadElement is used to render a table's head element.
|
||||
type TableHeadElement struct {
|
||||
}
|
||||
|
||||
// A TableCellElement is used to render a single cell in a row.
|
||||
type TableCellElement struct {
|
||||
Text string
|
||||
Head bool
|
||||
}
|
||||
|
||||
func (e *TableElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
bs := ctx.blockStack
|
||||
|
||||
var indentation uint
|
||||
var margin uint
|
||||
rules := ctx.options.Styles.Table
|
||||
if rules.Indent != nil {
|
||||
indentation = *rules.Indent
|
||||
}
|
||||
if rules.Margin != nil {
|
||||
margin = *rules.Margin
|
||||
}
|
||||
|
||||
iw := indent.NewWriterPipe(w, indentation+margin, func(wr io.Writer) {
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, " ")
|
||||
})
|
||||
|
||||
style := bs.With(rules.StylePrimitive)
|
||||
ctx.table.styleWriter = NewStyleWriter(ctx, iw, style)
|
||||
|
||||
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
|
||||
renderText(ctx.table.styleWriter, ctx.options.ColorProfile, style, rules.Prefix)
|
||||
ctx.table.writer = tablewriter.NewWriter(ctx.table.styleWriter)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *TableElement) Finish(w io.Writer, ctx RenderContext) error {
|
||||
rules := ctx.options.Styles.Table
|
||||
|
||||
ctx.table.writer.SetBorders(tablewriter.Border{Left: false, Top: false, Right: false, Bottom: false})
|
||||
if rules.CenterSeparator != nil {
|
||||
ctx.table.writer.SetCenterSeparator(*rules.CenterSeparator)
|
||||
}
|
||||
if rules.ColumnSeparator != nil {
|
||||
ctx.table.writer.SetColumnSeparator(*rules.ColumnSeparator)
|
||||
}
|
||||
if rules.RowSeparator != nil {
|
||||
ctx.table.writer.SetRowSeparator(*rules.RowSeparator)
|
||||
}
|
||||
|
||||
ctx.table.writer.Render()
|
||||
ctx.table.writer = nil
|
||||
|
||||
renderText(ctx.table.styleWriter, ctx.options.ColorProfile, ctx.blockStack.With(rules.StylePrimitive), rules.Suffix)
|
||||
renderText(ctx.table.styleWriter, ctx.options.ColorProfile, ctx.blockStack.Current().Style.StylePrimitive, rules.BlockSuffix)
|
||||
return ctx.table.styleWriter.Close()
|
||||
}
|
||||
|
||||
func (e *TableRowElement) Finish(w io.Writer, ctx RenderContext) error {
|
||||
if ctx.table.writer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.table.writer.Append(ctx.table.cell)
|
||||
ctx.table.cell = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *TableHeadElement) Finish(w io.Writer, ctx RenderContext) error {
|
||||
if ctx.table.writer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.table.writer.SetHeader(ctx.table.header)
|
||||
ctx.table.header = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *TableCellElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
if e.Head {
|
||||
ctx.table.header = append(ctx.table.header, e.Text)
|
||||
} else {
|
||||
ctx.table.cell = append(ctx.table.cell, e.Text)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
26
vendor/github.com/charmbracelet/glamour/ansi/task.go
generated
vendored
Normal file
26
vendor/github.com/charmbracelet/glamour/ansi/task.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// A TaskElement is used to render tasks inside a todo-list.
|
||||
type TaskElement struct {
|
||||
Checked bool
|
||||
}
|
||||
|
||||
func (e *TaskElement) Render(w io.Writer, ctx RenderContext) error {
|
||||
var el *BaseElement
|
||||
|
||||
pre := ctx.options.Styles.Task.Unticked
|
||||
if e.Checked {
|
||||
pre = ctx.options.Styles.Task.Ticked
|
||||
}
|
||||
|
||||
el = &BaseElement{
|
||||
Prefix: pre,
|
||||
Style: ctx.options.Styles.Task.StylePrimitive,
|
||||
}
|
||||
|
||||
return el.Render(w, ctx)
|
||||
}
|
||||
83
vendor/github.com/charmbracelet/glamour/ansi/templatehelper.go
generated
vendored
Normal file
83
vendor/github.com/charmbracelet/glamour/ansi/templatehelper.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// TemplateFuncMap contains a few useful template helpers
|
||||
var (
|
||||
TemplateFuncMap = template.FuncMap{
|
||||
"Left": func(values ...interface{}) string {
|
||||
s := values[0].(string)
|
||||
n := values[1].(int)
|
||||
if n > len(s) {
|
||||
n = len(s)
|
||||
}
|
||||
|
||||
return s[:n]
|
||||
},
|
||||
"Matches": func(values ...interface{}) bool {
|
||||
ok, _ := regexp.MatchString(values[1].(string), values[0].(string))
|
||||
return ok
|
||||
},
|
||||
"Mid": func(values ...interface{}) string {
|
||||
s := values[0].(string)
|
||||
l := values[1].(int)
|
||||
if l > len(s) {
|
||||
l = len(s)
|
||||
}
|
||||
|
||||
if len(values) > 2 {
|
||||
r := values[2].(int)
|
||||
if r > len(s) {
|
||||
r = len(s)
|
||||
}
|
||||
return s[l:r]
|
||||
}
|
||||
return s[l:]
|
||||
},
|
||||
"Right": func(values ...interface{}) string {
|
||||
s := values[0].(string)
|
||||
n := len(s) - values[1].(int)
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
|
||||
return s[n:]
|
||||
},
|
||||
"Last": func(values ...interface{}) string {
|
||||
return values[0].([]string)[len(values[0].([]string))-1]
|
||||
},
|
||||
// strings functions
|
||||
"Compare": strings.Compare, // 1.5+ only
|
||||
"Contains": strings.Contains,
|
||||
"ContainsAny": strings.ContainsAny,
|
||||
"Count": strings.Count,
|
||||
"EqualFold": strings.EqualFold,
|
||||
"HasPrefix": strings.HasPrefix,
|
||||
"HasSuffix": strings.HasSuffix,
|
||||
"Index": strings.Index,
|
||||
"IndexAny": strings.IndexAny,
|
||||
"Join": strings.Join,
|
||||
"LastIndex": strings.LastIndex,
|
||||
"LastIndexAny": strings.LastIndexAny,
|
||||
"Repeat": strings.Repeat,
|
||||
"Replace": strings.Replace,
|
||||
"Split": strings.Split,
|
||||
"SplitAfter": strings.SplitAfter,
|
||||
"SplitAfterN": strings.SplitAfterN,
|
||||
"SplitN": strings.SplitN,
|
||||
"Title": strings.Title,
|
||||
"ToLower": strings.ToLower,
|
||||
"ToTitle": strings.ToTitle,
|
||||
"ToUpper": strings.ToUpper,
|
||||
"Trim": strings.Trim,
|
||||
"TrimLeft": strings.TrimLeft,
|
||||
"TrimPrefix": strings.TrimPrefix,
|
||||
"TrimRight": strings.TrimRight,
|
||||
"TrimSpace": strings.TrimSpace,
|
||||
"TrimSuffix": strings.TrimSuffix,
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user