mirror of
https://github.com/alecthomas/chroma.git
synced 2025-07-01 00:35:06 +02:00
Document and add iterator panic recovery.
This commit is contained in:
@ -18,9 +18,6 @@ func main() {
|
|||||||
kingpin.CommandLine.Help = "Exercise linters against a list of files."
|
kingpin.CommandLine.Help = "Exercise linters against a list of files."
|
||||||
kingpin.Parse()
|
kingpin.Parse()
|
||||||
|
|
||||||
writer, err := formatters.NoOp.Format(ioutil.Discard, styles.SwapOff)
|
|
||||||
kingpin.FatalIfError(err, "")
|
|
||||||
|
|
||||||
for _, file := range *filesArgs {
|
for _, file := range *filesArgs {
|
||||||
lexer := lexers.Match(file)
|
lexer := lexers.Match(file)
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
@ -29,8 +26,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
text, err := ioutil.ReadFile(file)
|
text, err := ioutil.ReadFile(file)
|
||||||
kingpin.FatalIfError(err, "")
|
kingpin.FatalIfError(err, "")
|
||||||
err = lexer.Tokenise(nil, string(text), writer)
|
it, err := lexer.Tokenise(nil, string(text))
|
||||||
kingpin.FatalIfError(err, "%s failed to tokenise %q", lexer.Config().Name, file)
|
kingpin.FatalIfError(err, "%s failed to tokenise %q", lexer.Config().Name, file)
|
||||||
|
err = formatters.NoOp.Format(ioutil.Discard, styles.SwapOff, it)
|
||||||
|
kingpin.FatalIfError(err, "%s failed to format %q", lexer.Config().Name, file)
|
||||||
fmt.Printf("ok: %q\n", file)
|
fmt.Printf("ok: %q\n", file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
coalesce.go
12
coalesce.go
@ -1,13 +1,9 @@
|
|||||||
package chroma
|
package chroma
|
||||||
|
|
||||||
// Coalesce is a Lexer interceptor that collapses runs of common types into a single token.
|
// Coalesce is a Lexer interceptor that collapses runs of common types into a single token.
|
||||||
func Coalesce(lexer Lexer) Lexer {
|
func Coalesce(lexer Lexer) Lexer { return &coalescer{lexer} }
|
||||||
return &coalescer{lexer}
|
|
||||||
}
|
|
||||||
|
|
||||||
type coalescer struct {
|
type coalescer struct{ Lexer }
|
||||||
Lexer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
|
func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
|
||||||
var prev *Token
|
var prev *Token
|
||||||
@ -22,6 +18,10 @@ func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, e
|
|||||||
} else {
|
} else {
|
||||||
if prev.Type == token.Type && len(prev.Value) < 8192 {
|
if prev.Type == token.Type && len(prev.Value) < 8192 {
|
||||||
prev.Value += token.Value
|
prev.Value += token.Value
|
||||||
|
} else {
|
||||||
|
out := prev
|
||||||
|
prev = token
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
formatter.go
29
formatter.go
@ -7,10 +7,37 @@ import (
|
|||||||
// A Formatter for Chroma lexers.
|
// A Formatter for Chroma lexers.
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
// Format returns a formatting function for tokens.
|
// Format returns a formatting function for tokens.
|
||||||
|
//
|
||||||
|
// If the iterator panics, the Formatter should recover.
|
||||||
Format(w io.Writer, style *Style, iterator Iterator) error
|
Format(w io.Writer, style *Style, iterator Iterator) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// A FormatterFunc is a Formatter implemented as a function.
|
// A FormatterFunc is a Formatter implemented as a function.
|
||||||
|
//
|
||||||
|
// Guards against iterator panics.
|
||||||
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
|
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
|
||||||
|
|
||||||
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) error { return f(w, s, it) }
|
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if perr := recover(); perr != nil {
|
||||||
|
err = perr.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return f(w, s, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
type recoveringFormatter struct {
|
||||||
|
Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r recoveringFormatter) Format(w io.Writer, s *Style, it Iterator) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if perr := recover(); perr != nil {
|
||||||
|
err = perr.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return r.Formatter.Format(w, s, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveringFormatter wraps a formatter with panic recovery.
|
||||||
|
func RecoveringFormatter(formatter Formatter) Formatter { return recoveringFormatter{formatter} }
|
||||||
|
@ -67,8 +67,13 @@ func (h highlightRanges) Len() int { return len(h) }
|
|||||||
func (h highlightRanges) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
func (h highlightRanges) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||||
func (h highlightRanges) Less(i, j int) bool { return h[i][0] < h[j][0] }
|
func (h highlightRanges) Less(i, j int) bool { return h[i][0] < h[j][0] }
|
||||||
|
|
||||||
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) error {
|
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
|
||||||
return f.writeHTML(w, style, chroma.Flatten(iterator))
|
defer func() {
|
||||||
|
if perr := recover(); perr != nil {
|
||||||
|
err = perr.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return f.writeHTML(w, style, iterator.Tokens())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []*chroma.Token) error { // nolint: gocyclo
|
func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []*chroma.Token) error { // nolint: gocyclo
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package html
|
package html
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -49,3 +50,11 @@ func TestSplitTokensIntoLines(t *testing.T) {
|
|||||||
actual := splitTokensIntoLines(in)
|
actual := splitTokensIntoLines(in)
|
||||||
assert.Equal(t, expected, actual)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIteratorPanicRecovery(t *testing.T) {
|
||||||
|
it := func() *chroma.Token {
|
||||||
|
panic(errors.New("bad"))
|
||||||
|
}
|
||||||
|
err := New().Format(ioutil.Discard, styles.Fallback, it)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
@ -234,7 +234,12 @@ type indexedTTYFormatter struct {
|
|||||||
table *ttyTable
|
table *ttyTable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
|
func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if perr := recover(); perr != nil {
|
||||||
|
err = perr.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
theme := styleToEscapeSequence(c.table, style)
|
theme := styleToEscapeSequence(c.table, style)
|
||||||
for token := it(); token != nil; token = it() {
|
for token := it(); token != nil; token = it() {
|
||||||
// TODO: Cache token lookups?
|
// TODO: Cache token lookups?
|
||||||
|
20
iterator.go
20
iterator.go
@ -3,8 +3,19 @@ package chroma
|
|||||||
// An Iterator across tokens.
|
// An Iterator across tokens.
|
||||||
//
|
//
|
||||||
// nil will be returned at the end of the Token stream.
|
// nil will be returned at the end of the Token stream.
|
||||||
|
//
|
||||||
|
// If an error occurs within an Iterator, it may propagate this in a panic. Formatters should recover.
|
||||||
type Iterator func() *Token
|
type Iterator func() *Token
|
||||||
|
|
||||||
|
// Tokens consumes all tokens from the iterator and returns them as a slice.
|
||||||
|
func (i Iterator) Tokens() []*Token {
|
||||||
|
out := []*Token{}
|
||||||
|
for t := i(); t != nil; t = i() {
|
||||||
|
out = append(out, t)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// Concaterator concatenates tokens from a series of iterators.
|
// Concaterator concatenates tokens from a series of iterators.
|
||||||
func Concaterator(iterators ...Iterator) Iterator {
|
func Concaterator(iterators ...Iterator) Iterator {
|
||||||
return func() *Token {
|
return func() *Token {
|
||||||
@ -30,12 +41,3 @@ func Literator(tokens ...*Token) Iterator {
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten an Iterator into its tokens.
|
|
||||||
func Flatten(iterator Iterator) []*Token {
|
|
||||||
out := []*Token{}
|
|
||||||
for t := iterator(); t != nil; t = iterator() {
|
|
||||||
out = append(out, t)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
var Makefile = Register(MustNewLexer(
|
var Makefile = Register(MustNewLexer(
|
||||||
&Config{
|
&Config{
|
||||||
Name: "Base Makefile",
|
Name: "Base Makefile",
|
||||||
Aliases: []string{"make"},
|
Aliases: []string{"make", "makefile", "mf", "bsdmake"},
|
||||||
Filenames: []string{},
|
Filenames: []string{"*.mak", "*.mk", "Makefile", "makefile", "Makefile.*", "GNUmakefile"},
|
||||||
MimeTypes: []string{},
|
MimeTypes: []string{"text/x-makefile"},
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -55,7 +55,7 @@ func handleCodeblock(groups []string, lexer Lexer) Iterator {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
iterators = append(iterators, sub)
|
iterators = append(iterators, Literator(tokens...), sub)
|
||||||
}
|
}
|
||||||
iterators = append(iterators, Literator(&Token{String, groups[5]}))
|
iterators = append(iterators, Literator(&Token{String, groups[5]}))
|
||||||
return Concaterator(iterators...)
|
return Concaterator(iterators...)
|
||||||
|
@ -267,6 +267,7 @@ func (r *RegexLexer) Tokenise(options *TokeniseOptions, text string) (Iterator,
|
|||||||
options = defaultOptions
|
options = defaultOptions
|
||||||
}
|
}
|
||||||
state := &LexerState{
|
state := &LexerState{
|
||||||
|
Lexer: r,
|
||||||
Text: []rune(text),
|
Text: []rune(text),
|
||||||
Stack: []string{options.State},
|
Stack: []string{options.State},
|
||||||
Rules: r.rules,
|
Rules: r.rules,
|
||||||
|
Reference in New Issue
Block a user