Skip to content
Permalink
Browse files

Flexibility and extensions

  • Loading branch information...
Depado committed Oct 11, 2017
1 parent 605c7fe commit 2a3bd08c84089d7a9b2f55b44fc03e2266d811a1
Showing with 155 additions and 24 deletions.
  1. +59 −17 README.md
  2. +38 −0 example/main.go
  3. +58 −7 renderer.go
@@ -7,6 +7,10 @@

Integrating Chroma syntax highlighter as a blackfriday renderer

## Install

`go get -u github.com/Depado/bfchroma`

## Features

This renderer integrates chroma to highlight code with triple backtick notation.
@@ -29,13 +33,58 @@ chroma will fallback to a sane default as it can't determine the used language

## Usage

bfchroma uses the functional options approach so you can customize the behavior
of the renderer. It uses sane defaults when no option is passed so you can use
the renderer simply by doing so :

```go
bfchroma.Renderer{
Base: bf.NewHTMLRenderer(bf.HTMLRendererParameters{
Flags: bf.CommonHTMLFlags,
}),
Style: "monokai",
}
html := bf.Run([]byte(md), bf.WithRenderer(bfchroma.NewRenderer()))
```

### Options

- `Style(s string)`
Define the style used by chroma for the rendering. The full list can be found [here](https://github.com/alecthomas/chroma/tree/master/styles)
- `WithoutAutodetect()`
By default when no language information is written in the code block, this
renderer will try to auto-detect the used language. This option disables
this behavior and will fallback to a sane default when no language
information is avaiable.
- `Extend(bf.Renderer)`
This option allows to define the base blackfriday that will be extended.
- `ChromaOptions(...html.Option)`
This option allows you to pass Chroma's html options in the renderer. Such
options can be found [here](https://github.com/alecthomas/chroma#the-html-formatter).
There is currently an issue with the `html.WithClasses()` option as it expects
the CSS classes to be written separately. I'll come up with a fix later.

### Option examples

Disabling language auto-detection and displaying line numbers

```go
r := bfchroma.NewRenderer(
bfchroma.WithoutAutodetect(),
bfchroma.ChromaOptions(html.WithLineNumbers()),
)
```

Extend a blackfriday renderer

```go
b := bf.NewHTMLRenderer(bf.HTMLRendererParameters{
Flags: bf.CommonHTMLFlags,
})
r := bfchroma.NewRenderer(bfchroma.Extend(b))
```

Use a different style

```go
r := bfchroma.NewRenderer(bfchroma.Style("dracula"))
```

## Example
@@ -53,18 +102,12 @@ import (
var md = "This is some sample code.\n\n```go\n" +
`func main() {
fmt.Println("Hi")
fmt.Println("Hi")
}
` + "```"
func main() {
r := bfchroma.Renderer{
Base: bf.NewHTMLRenderer(bf.HTMLRendererParameters{
Flags: bf.CommonHTMLFlags,
}),
Style: "monokai",
}
html := bf.Run([]byte(md), bf.WithRenderer(&r))
html := bf.Run([]byte(md), bf.WithRenderer(bfchroma.NewRenderer()))
fmt.Println(string(html))
}
```
@@ -83,6 +126,5 @@ Will output :
## ToDo

- [ ] Add tests
- [ ] Add more flexibility (chroma capabilities)
- [ ] Add a function to set the theme
- [ ] Use directly `chroma.Style` in the structure
- [ ] Use directly `chroma.Style` in the structure ?
- [ ] Allow the use of `html.WithClasses()`
@@ -0,0 +1,38 @@
package main

import (
"fmt"

"github.com/Depado/bfchroma"
"github.com/alecthomas/chroma/formatters/html"

bf "gopkg.in/russross/blackfriday.v2"
)

var md = "This is some sample code.\n\n```go\n" +
`func main() {
fmt.Println("Hi")
}
` + "```"

func main() {
var r *bfchroma.Renderer
var h []byte

// Basic usage
r = bfchroma.NewRenderer()
h = bf.Run([]byte(md), bf.WithRenderer(r))
fmt.Println(string(h))

// Option examples and extending a specific blackfriday renderer
b := bf.NewHTMLRenderer(bf.HTMLRendererParameters{
Flags: bf.CommonHTMLFlags,
})
r = bfchroma.NewRenderer(
bfchroma.WithoutAutodetect(),
bfchroma.Extend(b),
bfchroma.ChromaOptions(html.WithLineNumbers()),
)
h = bf.Run([]byte(md), bf.WithRenderer(r))
fmt.Println(string(h))
}
@@ -7,15 +7,64 @@ import (
"github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"

bf "gopkg.in/russross/blackfriday.v2"
)

func (r *Renderer) renderWithChroma(w io.Writer, text []byte, data bf.CodeBlockData) error {
// Option defines the functional option type
type Option func(r *Renderer)

// Style is a function option allowing to set the style used by chroma
// Default : "monokai"
func Style(s string) Option {
return func(r *Renderer) {
r.Style = s
}
}

// WithoutAutodetect disables chroma's language detection when no codeblock
// extra information is given. It will fallback to a sane default instead of
// trying to detect the language.
func WithoutAutodetect() Option {
return func(r *Renderer) {
r.Autodetect = false
}
}

// ChromaOptions allows to pass Chroma html.Option such as Standalone()
// WithClasses(), ClassPrefix(prefix)...
func ChromaOptions(options ...html.Option) Option {
return func(r *Renderer) {
r.ChromaOptions = options
}
}

func Extend(br bf.Renderer) Option {
return func(r *Renderer) {
r.Base = br
}
}

// NewRenderer will return a new bfchroma renderer with sane defaults
func NewRenderer(options ...Option) *Renderer {
r := &Renderer{
Base: bf.NewHTMLRenderer(bf.HTMLRendererParameters{
Flags: bf.CommonHTMLFlags,
}),
Style: "monokai",
Autodetect: true,
}
for _, option := range options {
option(r)
}
return r
}

// RenderWithChroma will render the given text to the w io.Writer
func (r *Renderer) RenderWithChroma(w io.Writer, text []byte, data bf.CodeBlockData) error {
var lexer chroma.Lexer
if len(data.Info) > 0 {
lexer = lexers.Get(string(data.Info))
} else {
} else if r.Autodetect {
lexer = lexers.Analyse(string(text))
}
if lexer == nil {
@@ -25,7 +74,7 @@ func (r *Renderer) renderWithChroma(w io.Writer, text []byte, data bf.CodeBlockD
if cstyle == nil {
cstyle = styles.Fallback
}
formatter := html.New()
formatter := html.New(r.ChromaOptions...)
iterator, err := lexer.Tokenise(nil, string(text))
if err != nil {
return err
@@ -36,15 +85,17 @@ func (r *Renderer) renderWithChroma(w io.Writer, text []byte, data bf.CodeBlockD
// Renderer is a custom Blackfriday renderer that uses the capabilities of
// chroma to highlight code with triple backtick notation
type Renderer struct {
Base *bf.HTMLRenderer
Style string
Base bf.Renderer
Autodetect bool
Style string
ChromaOptions []html.Option
}

// RenderNode satisfies the Renderer interface
func (r *Renderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus {
switch node.Type {
case bf.CodeBlock:
if err := r.renderWithChroma(w, node.Literal, node.CodeBlockData); err != nil {
if err := r.RenderWithChroma(w, node.Literal, node.CodeBlockData); err != nil {
return r.Base.RenderNode(w, node, entering)
}
return bf.SkipChildren

0 comments on commit 2a3bd08

Please sign in to comment.
You can’t perform that action at this time.