Have a single Render function handle all Markdown rendering
This commit is contained in:
parent
8198cce59b
commit
456d55ead9
11 changed files with 56 additions and 189 deletions
|
|
@ -128,8 +128,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
theme := opts.IO.DetectTerminalTheme()
|
||||
markdownStyle := markdown.GetStyle(theme)
|
||||
opts.IO.DetectTerminalTheme()
|
||||
if err := opts.IO.StartPager(); err != nil {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "starting pager failed: %v\n", err)
|
||||
}
|
||||
|
|
@ -145,7 +144,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
}
|
||||
|
||||
if strings.Contains(gf.Type, "markdown") && !opts.Raw {
|
||||
rendered, err := markdown.Render(gf.Content, markdownStyle)
|
||||
rendered, err := markdown.Render(gf.Content, markdown.WithIO(opts.IO))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,8 +231,7 @@ func printHumanIssuePreview(opts *ViewOptions, issue *api.Issue) error {
|
|||
if issue.Body == "" {
|
||||
md = fmt.Sprintf("\n %s\n\n", cs.Gray("No description provided"))
|
||||
} else {
|
||||
style := markdown.GetStyle(opts.IO.TerminalTheme())
|
||||
md, err = markdown.Render(issue.Body, style)
|
||||
md, err = markdown.Render(issue.Body, markdown.WithIO(opts.IO))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,8 +273,7 @@ func reviewSurvey(io *iostreams.IOStreams, editorCommand string) (*api.PullReque
|
|||
}
|
||||
|
||||
if len(bodyAnswers.Body) > 0 {
|
||||
style := markdown.GetStyle(io.DetectTerminalTheme())
|
||||
renderedBody, err := markdown.Render(bodyAnswers.Body, style)
|
||||
renderedBody, err := markdown.Render(bodyAnswers.Body, markdown.WithIO(io))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,8 +123,7 @@ func formatComment(io *iostreams.IOStreams, comment Comment, newest bool) (strin
|
|||
if comment.Content() == "" {
|
||||
md = fmt.Sprintf("\n %s\n\n", cs.Gray("No body provided"))
|
||||
} else {
|
||||
style := markdown.GetStyle(io.TerminalTheme())
|
||||
md, err = markdown.Render(comment.Content(), style)
|
||||
md, err = markdown.Render(comment.Content(), markdown.WithIO(io))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,8 +215,7 @@ func printHumanPrPreview(opts *ViewOptions, pr *api.PullRequest) error {
|
|||
if pr.Body == "" {
|
||||
md = fmt.Sprintf("\n %s\n\n", cs.Gray("No description provided"))
|
||||
} else {
|
||||
style := markdown.GetStyle(opts.IO.TerminalTheme())
|
||||
md, err = markdown.Render(pr.Body, style)
|
||||
md, err = markdown.Render(pr.Body, markdown.WithIO(opts.IO))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,8 +141,7 @@ func renderReleaseTTY(io *iostreams.IOStreams, release *shared.Release) error {
|
|||
fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(*release.PublishedAt)))))
|
||||
}
|
||||
|
||||
style := markdown.GetStyle(io.TerminalTheme())
|
||||
renderedDescription, err := markdown.Render(release.Body, style)
|
||||
renderedDescription, err := markdown.Render(release.Body, markdown.WithIO(io))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,8 +187,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
readmeContent = cs.Gray("This repository does not have a README")
|
||||
} else if isMarkdownFile(readme.Filename) {
|
||||
var err error
|
||||
style := markdown.GetStyle(opts.IO.TerminalTheme())
|
||||
readmeContent, err = markdown.RenderWithBaseURL(readme.Content, style, readme.BaseURL)
|
||||
readmeContent, err = markdown.Render(readme.Content, markdown.WithIO(opts.IO), markdown.WithBaseURL(readme.BaseURL))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error rendering markdown: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,12 @@ import (
|
|||
func referenceHelpFn(io *iostreams.IOStreams) func(*cobra.Command, []string) {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
wrapWidth := 0
|
||||
style := "notty"
|
||||
if io.IsStdoutTTY() {
|
||||
io.DetectTerminalTheme()
|
||||
wrapWidth = io.TerminalWidth()
|
||||
style = markdown.GetStyle(io.DetectTerminalTheme())
|
||||
}
|
||||
|
||||
md, err := markdown.RenderWithWrap(cmd.Long, style, wrapWidth)
|
||||
md, err := markdown.Render(cmd.Long, markdown.WithIO(io), markdown.WithWrap(wrapWidth))
|
||||
if err != nil {
|
||||
fmt.Fprintln(io.ErrOut, err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -156,8 +156,7 @@ func viewWorkflowContent(opts *ViewOptions, client *api.Client, workflow *shared
|
|||
|
||||
yaml := string(yamlBytes)
|
||||
|
||||
theme := opts.IO.DetectTerminalTheme()
|
||||
markdownStyle := markdown.GetStyle(theme)
|
||||
opts.IO.DetectTerminalTheme()
|
||||
if err := opts.IO.StartPager(); err != nil {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "starting pager failed: %v\n", err)
|
||||
}
|
||||
|
|
@ -172,11 +171,7 @@ func viewWorkflowContent(opts *ViewOptions, client *api.Client, workflow *shared
|
|||
fmt.Fprintf(out, "ID: %s", cs.Cyanf("%d", workflow.ID))
|
||||
|
||||
codeBlock := fmt.Sprintf("```yaml\n%s\n```", yaml)
|
||||
rendered, err := markdown.RenderWithOpts(codeBlock, markdownStyle,
|
||||
markdown.RenderOpts{
|
||||
markdown.WithoutIndentation(),
|
||||
markdown.WithoutWrap(),
|
||||
})
|
||||
rendered, err := markdown.Render(codeBlock, markdown.WithIO(opts.IO), markdown.WithoutIndentation(), markdown.WithWrap(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import (
|
|||
"github.com/charmbracelet/glamour"
|
||||
)
|
||||
|
||||
type RenderOpts []glamour.TermRendererOption
|
||||
|
||||
func WithoutIndentation() glamour.TermRendererOption {
|
||||
overrides := []byte(`
|
||||
{
|
||||
|
|
@ -23,15 +21,38 @@ func WithoutIndentation() glamour.TermRendererOption {
|
|||
return glamour.WithStylesFromJSONBytes(overrides)
|
||||
}
|
||||
|
||||
func WithoutWrap() glamour.TermRendererOption {
|
||||
return glamour.WithWordWrap(0)
|
||||
func WithWrap(w int) glamour.TermRendererOption {
|
||||
return glamour.WithWordWrap(w)
|
||||
}
|
||||
|
||||
func render(text string, opts RenderOpts) (string, error) {
|
||||
type IOStreams interface {
|
||||
TerminalTheme() string
|
||||
}
|
||||
|
||||
func WithIO(io IOStreams) glamour.TermRendererOption {
|
||||
style := os.Getenv("GLAMOUR_STYLE")
|
||||
if style == "" || style == "auto" {
|
||||
theme := io.TerminalTheme()
|
||||
switch theme {
|
||||
case "light", "dark":
|
||||
style = theme
|
||||
default:
|
||||
style = "notty"
|
||||
}
|
||||
}
|
||||
return glamour.WithStylePath(style)
|
||||
}
|
||||
|
||||
func WithBaseURL(u string) glamour.TermRendererOption {
|
||||
return glamour.WithBaseURL(u)
|
||||
}
|
||||
|
||||
func Render(text string, opts ...glamour.TermRendererOption) (string, error) {
|
||||
// Glamour rendering preserves carriage return characters in code blocks, but
|
||||
// we need to ensure that no such characters are present in the output.
|
||||
text = strings.ReplaceAll(text, "\r\n", "\n")
|
||||
|
||||
opts = append(opts, glamour.WithEmoji())
|
||||
tr, err := glamour.NewTermRenderer(opts...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -39,59 +60,3 @@ func render(text string, opts RenderOpts) (string, error) {
|
|||
|
||||
return tr.Render(text)
|
||||
}
|
||||
|
||||
func Render(text, style string) (string, error) {
|
||||
opts := RenderOpts{
|
||||
glamour.WithStylePath(style),
|
||||
glamour.WithEmoji(),
|
||||
}
|
||||
|
||||
return render(text, opts)
|
||||
}
|
||||
|
||||
func RenderWithOpts(text, style string, opts RenderOpts) (string, error) {
|
||||
defaultOpts := RenderOpts{
|
||||
glamour.WithStylePath(style),
|
||||
glamour.WithEmoji(),
|
||||
}
|
||||
opts = append(defaultOpts, opts...)
|
||||
|
||||
return render(text, opts)
|
||||
}
|
||||
|
||||
func RenderWithBaseURL(text, style, baseURL string) (string, error) {
|
||||
opts := RenderOpts{
|
||||
glamour.WithStylePath(style),
|
||||
glamour.WithEmoji(),
|
||||
glamour.WithBaseURL(baseURL),
|
||||
}
|
||||
|
||||
return render(text, opts)
|
||||
}
|
||||
|
||||
func RenderWithWrap(text, style string, wrap int) (string, error) {
|
||||
opts := RenderOpts{
|
||||
glamour.WithStylePath(style),
|
||||
glamour.WithEmoji(),
|
||||
glamour.WithWordWrap(wrap),
|
||||
}
|
||||
|
||||
return render(text, opts)
|
||||
}
|
||||
|
||||
func GetStyle(defaultStyle string) string {
|
||||
style := fromEnv()
|
||||
if style != "" && style != "auto" {
|
||||
return style
|
||||
}
|
||||
|
||||
if defaultStyle == "light" || defaultStyle == "dark" {
|
||||
return defaultStyle
|
||||
}
|
||||
|
||||
return "notty"
|
||||
}
|
||||
|
||||
var fromEnv = func() string {
|
||||
return os.Getenv("GLAMOUR_STYLE")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,46 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Render(t *testing.T) {
|
||||
os.Unsetenv("GLAMOUR_STYLE")
|
||||
|
||||
type input struct {
|
||||
text string
|
||||
style string
|
||||
}
|
||||
type output struct {
|
||||
wantsErr bool
|
||||
theme string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
input input
|
||||
output output
|
||||
name string
|
||||
input input
|
||||
wantsErr bool
|
||||
}{
|
||||
{
|
||||
name: "invalid glamour style",
|
||||
name: "light theme",
|
||||
input: input{
|
||||
text: "some text",
|
||||
style: "invalid",
|
||||
},
|
||||
output: output{
|
||||
wantsErr: true,
|
||||
theme: "light",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid glamour style",
|
||||
name: "dark theme",
|
||||
input: input{
|
||||
text: "some text",
|
||||
style: "dark",
|
||||
},
|
||||
output: output{
|
||||
wantsErr: false,
|
||||
theme: "dark",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := Render(tt.input.text, tt.input.style)
|
||||
if tt.output.wantsErr {
|
||||
_, err := Render(tt.input.text, WithIO(terminalThemer(tt.input.theme)))
|
||||
if tt.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
|
@ -53,89 +49,8 @@ func Test_Render(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_GetStyle(t *testing.T) {
|
||||
type input struct {
|
||||
glamourStyle string
|
||||
terminalTheme string
|
||||
}
|
||||
type output struct {
|
||||
style string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
input input
|
||||
output output
|
||||
}{
|
||||
{
|
||||
name: "no glamour style and no terminal theme",
|
||||
input: input{
|
||||
glamourStyle: "",
|
||||
terminalTheme: "none",
|
||||
},
|
||||
output: output{
|
||||
style: "notty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto glamour style and no terminal theme",
|
||||
input: input{
|
||||
glamourStyle: "auto",
|
||||
terminalTheme: "none",
|
||||
},
|
||||
output: output{
|
||||
style: "notty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user glamour style and no terminal theme",
|
||||
input: input{
|
||||
glamourStyle: "somestyle",
|
||||
terminalTheme: "none",
|
||||
},
|
||||
output: output{
|
||||
style: "somestyle",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no glamour style and light terminal theme",
|
||||
input: input{
|
||||
glamourStyle: "",
|
||||
terminalTheme: "light",
|
||||
},
|
||||
output: output{
|
||||
style: "light",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no glamour style and dark terminal theme",
|
||||
input: input{
|
||||
glamourStyle: "",
|
||||
terminalTheme: "dark",
|
||||
},
|
||||
output: output{
|
||||
style: "dark",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no glamour style and unknown terminal theme",
|
||||
input: input{
|
||||
glamourStyle: "",
|
||||
terminalTheme: "unknown",
|
||||
},
|
||||
output: output{
|
||||
style: "notty",
|
||||
},
|
||||
},
|
||||
}
|
||||
type terminalThemer string
|
||||
|
||||
for _, tt := range tests {
|
||||
fromEnv = func() string {
|
||||
return tt.input.glamourStyle
|
||||
}
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
style := GetStyle(tt.input.terminalTheme)
|
||||
assert.Equal(t, tt.output.style, style)
|
||||
})
|
||||
}
|
||||
func (tt terminalThemer) TerminalTheme() string {
|
||||
return string(tt)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue