Use better dep version
This version of dep is better at pruning unused source code from dependencies, so we don't need to carry around such a large vendor directory.
This commit is contained in:
parent
d441569025
commit
5b9795d676
|
@ -1,3 +1,7 @@
|
|||
[prune]
|
||||
unused-packages = true
|
||||
go-tests = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/alecthomas/chroma"
|
||||
revision = "v0.2.1"
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/aymerick/douceur/css"
|
||||
"github.com/aymerick/douceur/parser"
|
||||
"gopkg.in/alecthomas/kingpin.v3-unstable"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
const (
|
||||
outputTemplate = `package styles
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// {{.Name}} style.
|
||||
var {{.Name}} = Register(chroma.MustNewStyle("{{.Name|Lower}}", chroma.StyleEntries{
|
||||
{{- range .Rules}}
|
||||
{{- if .Prelude|TokenType}}
|
||||
chroma.{{.Prelude|TokenType}}: "{{.Declarations|TranslateDecls}}",
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}))
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
typeByClass = map[string]chroma.TokenType{
|
||||
".hll": chroma.Background,
|
||||
}
|
||||
|
||||
cssNamedColours = map[string]string{
|
||||
"black": "#000000", "silver": "#c0c0c0", "gray": "#808080", "white": "#ffffff",
|
||||
"maroon": "#800000", "red": "#ff0000", "purple": "#800080", "fuchsia": "#ff00ff",
|
||||
"green": "#008000", "lime": "#00ff00", "olive": "#808000", "yellow": "#ffff00",
|
||||
"navy": "#000080", "blue": "#0000ff", "teal": "#008080", "aqua": "#00ffff",
|
||||
"orange": "#ffa500", "aliceblue": "#f0f8ff", "antiquewhite": "#faebd7", "aquamarine": "#7fffd4",
|
||||
"azure": "#f0ffff", "beige": "#f5f5dc", "bisque": "#ffe4c4", "blanchedalmond": "#ffebcd",
|
||||
"blueviolet": "#8a2be2", "brown": "#a52a2a", "burlywood": "#deb887", "cadetblue": "#5f9ea0",
|
||||
"chartreuse": "#7fff00", "chocolate": "#d2691e", "coral": "#ff7f50", "cornflowerblue": "#6495ed",
|
||||
"cornsilk": "#fff8dc", "crimson": "#dc143c", "cyan": "#00ffff", "darkblue": "#00008b",
|
||||
"darkcyan": "#008b8b", "darkgoldenrod": "#b8860b", "darkgray": "#a9a9a9", "darkgreen": "#006400",
|
||||
"darkgrey": "#a9a9a9", "darkkhaki": "#bdb76b", "darkmagenta": "#8b008b", "darkolivegreen": "#556b2f",
|
||||
"darkorange": "#ff8c00", "darkorchid": "#9932cc", "darkred": "#8b0000", "darksalmon": "#e9967a",
|
||||
"darkseagreen": "#8fbc8f", "darkslateblue": "#483d8b", "darkslategray": "#2f4f4f", "darkslategrey": "#2f4f4f",
|
||||
"darkturquoise": "#00ced1", "darkviolet": "#9400d3", "deeppink": "#ff1493", "deepskyblue": "#00bfff",
|
||||
"dimgray": "#696969", "dimgrey": "#696969", "dodgerblue": "#1e90ff", "firebrick": "#b22222",
|
||||
"floralwhite": "#fffaf0", "forestgreen": "#228b22", "gainsboro": "#dcdcdc", "ghostwhite": "#f8f8ff",
|
||||
"gold": "#ffd700", "goldenrod": "#daa520", "greenyellow": "#adff2f", "grey": "#808080",
|
||||
"honeydew": "#f0fff0", "hotpink": "#ff69b4", "indianred": "#cd5c5c", "indigo": "#4b0082",
|
||||
"ivory": "#fffff0", "khaki": "#f0e68c", "lavender": "#e6e6fa", "lavenderblush": "#fff0f5",
|
||||
"lawngreen": "#7cfc00", "lemonchiffon": "#fffacd", "lightblue": "#add8e6", "lightcoral": "#f08080",
|
||||
"lightcyan": "#e0ffff", "lightgoldenrodyellow": "#fafad2", "lightgray": "#d3d3d3", "lightgreen": "#90ee90",
|
||||
"lightgrey": "#d3d3d3", "lightpink": "#ffb6c1", "lightsalmon": "#ffa07a", "lightseagreen": "#20b2aa",
|
||||
"lightskyblue": "#87cefa", "lightslategray": "#778899", "lightslategrey": "#778899", "lightsteelblue": "#b0c4de",
|
||||
"lightyellow": "#ffffe0", "limegreen": "#32cd32", "linen": "#faf0e6", "magenta": "#ff00ff",
|
||||
"mediumaquamarine": "#66cdaa", "mediumblue": "#0000cd", "mediumorchid": "#ba55d3", "mediumpurple": "#9370db",
|
||||
"mediumseagreen": "#3cb371", "mediumslateblue": "#7b68ee", "mediumspringgreen": "#00fa9a", "mediumturquoise": "#48d1cc",
|
||||
"mediumvioletred": "#c71585", "midnightblue": "#191970", "mintcream": "#f5fffa", "mistyrose": "#ffe4e1",
|
||||
"moccasin": "#ffe4b5", "navajowhite": "#ffdead", "oldlace": "#fdf5e6", "olivedrab": "#6b8e23",
|
||||
"orangered": "#ff4500", "orchid": "#da70d6", "palegoldenrod": "#eee8aa", "palegreen": "#98fb98",
|
||||
"paleturquoise": "#afeeee", "palevioletred": "#db7093", "papayawhip": "#ffefd5", "peachpuff": "#ffdab9",
|
||||
"peru": "#cd853f", "pink": "#ffc0cb", "plum": "#dda0dd", "powderblue": "#b0e0e6",
|
||||
"rosybrown": "#bc8f8f", "royalblue": "#4169e1", "saddlebrown": "#8b4513", "salmon": "#fa8072",
|
||||
"sandybrown": "#f4a460", "seagreen": "#2e8b57", "seashell": "#fff5ee", "sienna": "#a0522d",
|
||||
"skyblue": "#87ceeb", "slateblue": "#6a5acd", "slategray": "#708090", "slategrey": "#708090",
|
||||
"snow": "#fffafa", "springgreen": "#00ff7f", "steelblue": "#4682b4", "tan": "#d2b48c",
|
||||
"thistle": "#d8bfd8", "tomato": "#ff6347", "turquoise": "#40e0d0", "violet": "#ee82ee",
|
||||
"wheat": "#f5deb3", "whitesmoke": "#f5f5f5", "yellowgreen": "#9acd32", "rebeccapurple": "#663399",
|
||||
}
|
||||
|
||||
nameArg = kingpin.Arg("name", "Name of output style.").Required().String()
|
||||
fileArg = kingpin.Arg("stylesheets", ".css file to import").Required().ExistingFile()
|
||||
)
|
||||
|
||||
func init() {
|
||||
for tt, str := range chroma.StandardTypes {
|
||||
typeByClass["."+str] = tt
|
||||
}
|
||||
}
|
||||
|
||||
func translateDecls(decls []*css.Declaration) string {
|
||||
out := []string{}
|
||||
for _, decl := range decls {
|
||||
switch decl.Property {
|
||||
case "color":
|
||||
clr := decl.Value
|
||||
if c, ok := cssNamedColours[clr]; ok {
|
||||
clr = c
|
||||
}
|
||||
out = append(out, clr)
|
||||
case "background-color":
|
||||
out = append(out, "bg:"+decl.Value)
|
||||
case "font-style":
|
||||
if strings.Contains(decl.Value, "italic") {
|
||||
out = append(out, "italic")
|
||||
}
|
||||
case "font-weight":
|
||||
if strings.Contains(decl.Value, "bold") {
|
||||
out = append(out, "bold")
|
||||
}
|
||||
case "text-decoration":
|
||||
if strings.Contains(decl.Value, "underline") {
|
||||
out = append(out, "underline")
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func main() {
|
||||
kingpin.Parse()
|
||||
source, err := ioutil.ReadFile(*fileArg)
|
||||
kingpin.FatalIfError(err, "")
|
||||
css, err := parser.Parse(string(source))
|
||||
kingpin.FatalIfError(err, "")
|
||||
|
||||
context := map[string]interface{}{
|
||||
"Name": *nameArg,
|
||||
"Rules": css.Rules,
|
||||
}
|
||||
tmpl := template.Must(template.New("style").Funcs(template.FuncMap{
|
||||
"Lower": strings.ToLower,
|
||||
"TranslateDecls": translateDecls,
|
||||
"TokenType": func(s string) chroma.TokenType { return typeByClass[s] },
|
||||
}).Parse(outputTemplate))
|
||||
err = tmpl.Execute(os.Stdout, context)
|
||||
kingpin.FatalIfError(err, "")
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"gopkg.in/alecthomas/kingpin.v3-unstable"
|
||||
)
|
||||
|
||||
var (
|
||||
filesArgs = kingpin.Arg("file", "Files to use to exercise lexers.").Required().ExistingFiles()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.CommandLine.Help = "Exercise linters against a list of files."
|
||||
kingpin.Parse()
|
||||
|
||||
for _, file := range *filesArgs {
|
||||
lexer := lexers.Match(file)
|
||||
if lexer == nil {
|
||||
fmt.Printf("warning: could not find lexer for %q\n", file)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%s: ", file)
|
||||
os.Stdout.Sync()
|
||||
text, err := ioutil.ReadFile(file)
|
||||
kingpin.FatalIfError(err, "")
|
||||
it, err := lexer.Tokenise(nil, string(text))
|
||||
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\n")
|
||||
}
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
import functools
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pystache
|
||||
from pygments import lexer as pygments_lexer
|
||||
from pygments.token import _TokenType
|
||||
|
||||
|
||||
TEMPLATE = r'''
|
||||
package lexers
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
)
|
||||
|
||||
// {{upper_name}} lexer.
|
||||
var {{upper_name}} = Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "{{name}}",
|
||||
Aliases: []string{ {{#aliases}}"{{.}}", {{/aliases}} },
|
||||
Filenames: []string{ {{#filenames}}"{{.}}", {{/filenames}} },
|
||||
MimeTypes: []string{ {{#mimetypes}}"{{.}}", {{/mimetypes}} },
|
||||
{{#re_not_multiline}}
|
||||
NotMultiline: true,
|
||||
{{/re_not_multiline}}
|
||||
{{#re_dotall}}
|
||||
DotAll: true,
|
||||
{{/re_dotall}}
|
||||
{{#re_ignorecase}}
|
||||
CaseInsensitive: true,
|
||||
{{/re_ignorecase}}
|
||||
},
|
||||
Rules{
|
||||
{{#tokens}}
|
||||
"{{state}}": {
|
||||
{{#rules}}
|
||||
{{{.}}},
|
||||
{{/rules}}
|
||||
},
|
||||
{{/tokens}}
|
||||
},
|
||||
))
|
||||
'''
|
||||
|
||||
|
||||
def go_regex(s):
|
||||
return go_string(s)
|
||||
|
||||
|
||||
def go_string(s):
|
||||
if '`' not in s:
|
||||
return '`' + s + '`'
|
||||
return json.dumps(s)
|
||||
|
||||
|
||||
def to_camel_case(snake_str):
|
||||
components = snake_str.split('_')
|
||||
return ''.join(x.title() for x in components)
|
||||
|
||||
|
||||
def warning(message):
|
||||
print('warning: ' + message, file=sys.stderr)
|
||||
|
||||
|
||||
def resolve_emitter(emitter):
|
||||
if isinstance(emitter, types.FunctionType):
|
||||
if repr(emitter).startswith('<function bygroups.'):
|
||||
args = emitter.__closure__[0].cell_contents
|
||||
emitter = 'ByGroups(%s)' % ', '.join(resolve_emitter(e) for e in args)
|
||||
elif repr(emitter).startswith('<function using.'):
|
||||
args = emitter.__closure__[0].cell_contents
|
||||
if isinstance(args, dict):
|
||||
state = 'root'
|
||||
if 'stack' in args:
|
||||
state = args['stack'][1]
|
||||
args.pop('stack')
|
||||
assert args == {}, args
|
||||
emitter = 'UsingSelf("%s")' % state
|
||||
elif issubclass(args, pygments_lexer.Lexer):
|
||||
name = args.__name__
|
||||
if name.endswith('Lexer'):
|
||||
name = name[:-5]
|
||||
emitter = 'Using(%s, nil)' % name
|
||||
else:
|
||||
raise ValueError('only support "using" with lexer classes, not %r' % args)
|
||||
else:
|
||||
warning('unsupported emitter function %r' % emitter)
|
||||
emitter = '?? %r ??' % emitter
|
||||
elif isinstance(emitter, _TokenType):
|
||||
emitter = str(emitter).replace('.', '')[5:]
|
||||
elif emitter is None:
|
||||
# This generally only occurs when a lookahead/behind assertion is used, so we just allow it
|
||||
# through.
|
||||
return 'None'
|
||||
else:
|
||||
raise ValueError('unsupported emitter type %r' % emitter)
|
||||
assert isinstance(emitter, str)
|
||||
return emitter
|
||||
|
||||
|
||||
def process_state_action(action):
|
||||
if isinstance(action, tuple):
|
||||
return functools.reduce(lambda a, b: a + b, (process_state_action(a) for a in action))
|
||||
if action.startswith('#'):
|
||||
action = action[1:]
|
||||
if action== 'pop':
|
||||
action = 'Pop(1)'
|
||||
elif action.startswith('pop:'):
|
||||
action = 'Pop(%s)' % action[4:]
|
||||
elif action == 'push':
|
||||
action = 'Push()'
|
||||
elif action.startswith('push:'):
|
||||
action = 'Push("%s")' % action[5:]
|
||||
else:
|
||||
raise ValueError('unsupported action %r' % (action,))
|
||||
else:
|
||||
action = 'Push("%s")' % action
|
||||
return (action,)
|
||||
|
||||
|
||||
def translate_rules(rules):
|
||||
out = []
|
||||
for rule in rules:
|
||||
if isinstance(rule, tuple):
|
||||
regex = rule[0]
|
||||
if isinstance(regex, str):
|
||||
regex = go_regex(regex)
|
||||
elif isinstance(regex, pygments_lexer.words):
|
||||
regex = 'Words(%s, %s, %s)' % (go_string(regex.prefix),
|
||||
go_string(regex.suffix),
|
||||
', '.join(go_string(w) for w in regex.words))
|
||||
else:
|
||||
raise ValueError('expected regex string but got %r' % regex)
|
||||
emitter = resolve_emitter(rule[1])
|
||||
if len(rule) == 2:
|
||||
modifier = 'nil'
|
||||
elif type(rule[2]) is str:
|
||||
modifier = process_state_action(rule[2])[0]
|
||||
elif isinstance(rule[2], pygments_lexer.combined):
|
||||
modifier = 'Combined("%s")' % '", "'.join(rule[2])
|
||||
elif type(rule[2]) is tuple:
|
||||
modifier = 'Push("%s")' % '", "'.join(rule[2])
|
||||
else:
|
||||
raise ValueError('unsupported modifier %r' % (rule[2],))
|
||||
out.append('{{ {}, {}, {} }}'.format(regex, emitter, modifier))
|
||||
elif isinstance(rule, pygments_lexer.include):
|
||||
out.append('Include("{}")'.format(rule))
|
||||
elif isinstance(rule, pygments_lexer.default):
|
||||
out.append('Default({})'.format(', '.join(process_state_action(rule.state))))
|
||||
else:
|
||||
raise ValueError('unsupported rule %r' % (rule,))
|
||||
return out
|
||||
|
||||
|
||||
class TemplateView(object):
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
def re_not_multiline(self):
|
||||
return not (self.regex_flags & re.MULTILINE)
|
||||
|
||||
def re_dotall(self):
|
||||
return self.regex_flags & re.DOTALL
|
||||
|
||||
def re_ignorecase(self):
|
||||
return self.regex_flags & re.IGNORECASE
|
||||
|
||||
|
||||
def main():
|
||||
package_name, symbol_name = sys.argv[1].rsplit(sep=".", maxsplit=1)
|
||||
|
||||
package = importlib.import_module(package_name)
|
||||
|
||||
lexer_cls = getattr(package, symbol_name)
|
||||
|
||||
assert issubclass(lexer_cls, pygments_lexer.RegexLexer), 'can only translate from RegexLexer'
|
||||
|
||||
print(pystache.render(TEMPLATE, TemplateView(
|
||||
name=lexer_cls.name,
|
||||
regex_flags=lexer_cls.flags,
|
||||
upper_name=to_camel_case(lexer_cls.name),
|
||||
aliases=lexer_cls.aliases,
|
||||
filenames=lexer_cls.filenames,
|
||||
mimetypes=lexer_cls.mimetypes,
|
||||
tokens=[{'state': state, 'rules': translate_rules(rules)} for (state, rules) in lexer_cls.get_tokendefs().items()],
|
||||
)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,62 +0,0 @@
|
|||
import importlib
|
||||
import sys
|
||||
|
||||
import pystache
|
||||
from pygments.style import Style
|
||||
from pygments.token import Token
|
||||
|
||||
|
||||
TEMPLATE = r'''
|
||||
package styles
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// {{upper_name}} style.
|
||||
var {{upper_name}} = Register(chroma.MustNewStyle("{{name}}", chroma.StyleEntries{
|
||||
{{#styles}}
|
||||
chroma.{{type}}: "{{style}}",
|
||||
{{/styles}}
|
||||
}))
|
||||
'''
|
||||
|
||||
|
||||
def to_camel_case(snake_str):
|
||||
components = snake_str.split('_')
|
||||
return ''.join(x.title() for x in components)
|
||||
|
||||
|
||||
def translate_token_type(t):
|
||||
if t == Token:
|
||||
t = Token.Background
|
||||
return "".join(map(str, t))
|
||||
|
||||
|
||||
def main():
|
||||
name = sys.argv[1]
|
||||
package_name, symbol_name = sys.argv[2].rsplit(sep=".", maxsplit=1)
|
||||
|
||||
package = importlib.import_module(package_name)
|
||||
|
||||
style_cls = getattr(package, symbol_name)
|
||||
|
||||
assert issubclass(style_cls, Style), 'can only translate from Style subclass'
|
||||
|
||||
styles = dict(style_cls.styles)
|
||||
bg = "bg:" + style_cls.background_color
|
||||
if Token in styles:
|
||||
styles[Token] += " " + bg
|
||||
else:
|
||||
styles[Token] = bg
|
||||
context = {
|
||||
'upper_name': style_cls.__name__[:-5],
|
||||
'name': name,
|
||||
'styles': [{'type': translate_token_type(t), 'style': s}
|
||||
for t, s in styles.items() if s],
|
||||
}
|
||||
print(pystache.render(TEMPLATE, context))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,269 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"gopkg.in/alecthomas/kingpin.v3-unstable"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/formatters/html"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
)
|
||||
|
||||
var (
|
||||
// Populated by goreleaser.
|
||||
version = "?"
|
||||
commit = "?"
|
||||
date = "?"
|
||||
|
||||
profileFlag = kingpin.Flag("profile", "Enable profiling to file.").Hidden().String()
|
||||
listFlag = kingpin.Flag("list", "List lexers, styles and formatters.").Bool()
|
||||
unbufferedFlag = kingpin.Flag("unbuffered", "Do not buffer output.").Bool()
|
||||
traceFlag = kingpin.Flag("trace", "Trace lexer states as they are traversed.").Bool()
|
||||
checkFlag = kingpin.Flag("check", "Do not format, check for tokenization errors instead.").Bool()
|
||||
filenameFlag = kingpin.Flag("filename", "Filename to use for selecting a lexer when reading from stdin.").String()
|
||||
|
||||
lexerFlag = kingpin.Flag("lexer", "Lexer to use when formatting.").PlaceHolder("autodetect").Short('l').Enum(lexers.Names(true)...)
|
||||
styleFlag = kingpin.Flag("style", "Style to use for formatting.").Short('s').Default("swapoff").Enum(styles.Names()...)
|
||||
formatterFlag = kingpin.Flag("formatter", "Formatter to use.").Default("terminal").Short('f').Enum(formatters.Names()...)
|
||||
|
||||
jsonFlag = kingpin.Flag("json", "Output JSON representation of tokens.").Bool()
|
||||
|
||||
htmlFlag = kingpin.Flag("html", "Enable HTML mode (equivalent to '--formatter html').").Bool()
|
||||
htmlPrefixFlag = kingpin.Flag("html-prefix", "HTML CSS class prefix.").PlaceHolder("PREFIX").String()
|
||||
htmlStylesFlag = kingpin.Flag("html-styles", "Output HTML CSS styles.").Bool()
|
||||
htmlOnlyFlag = kingpin.Flag("html-only", "Output HTML fragment.").Bool()
|
||||
htmlInlineStyleFlag = kingpin.Flag("html-inline-styles", "Output HTML with inline styles (no classes).").Bool()
|
||||
htmlTabWidthFlag = kingpin.Flag("html-tab-width", "Set the HTML tab width.").Default("8").Int()
|
||||
htmlLinesFlag = kingpin.Flag("html-lines", "Include line numbers in output.").Bool()
|
||||
htmlLinesTableFlag = kingpin.Flag("html-lines-table", "Split line numbers and code in a HTML table").Bool()
|
||||
htmlLinesStyleFlag = kingpin.Flag("html-lines-style", "Style for line numbers.").String()
|
||||
htmlHighlightFlag = kingpin.Flag("html-highlight", "Highlight these lines.").PlaceHolder("N[:M][,...]").String()
|
||||
htmlHighlightStyleFlag = kingpin.Flag("html-highlight-style", "Style used for highlighting lines.").String()
|
||||
htmlBaseLineFlag = kingpin.Flag("html-base-line", "Base line number.").Default("1").Int()
|
||||
|
||||
filesArgs = kingpin.Arg("files", "Files to highlight.").ExistingFiles()
|
||||
)
|
||||
|
||||
type flushableWriter interface {
|
||||
io.Writer
|
||||
Flush() error
|
||||
}
|
||||
|
||||
type nopFlushableWriter struct{ io.Writer }
|
||||
|
||||
func (n *nopFlushableWriter) Flush() error { return nil }
|
||||
|
||||
func main() {
|
||||
kingpin.CommandLine.Version(fmt.Sprintf("%s-%s-%s", version, commit, date))
|
||||
kingpin.CommandLine.Help = `
|
||||
Chroma is a general purpose syntax highlighting library and corresponding
|
||||
command, for Go.
|
||||
`
|
||||
kingpin.Parse()
|
||||
if *listFlag {
|
||||
listAll()
|
||||
return
|
||||
}
|
||||
if *profileFlag != "" {
|
||||
f, err := os.Create(*profileFlag)
|
||||
kingpin.FatalIfError(err, "")
|
||||
pprof.StartCPUProfile(f)
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, os.Interrupt)
|
||||
go func() {
|
||||
<-signals
|
||||
pprof.StopCPUProfile()
|
||||
os.Exit(128 + 3)
|
||||
}()
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
var out io.Writer = os.Stdout
|
||||
if runtime.GOOS == "windows" && isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
out = colorable.NewColorableStdout()
|
||||
}
|
||||
var w flushableWriter
|
||||
if *unbufferedFlag {
|
||||
w = &nopFlushableWriter{out}
|
||||
} else {
|
||||
w = bufio.NewWriterSize(out, 16384)
|
||||
}
|
||||
defer w.Flush()
|
||||
|
||||
if *jsonFlag {
|
||||
*formatterFlag = "json"
|
||||
}
|
||||
|
||||
if *htmlFlag {
|
||||
*formatterFlag = "html"
|
||||
}
|
||||
|
||||
// Retrieve user-specified style, clone it, and add some overrides.
|
||||
builder := styles.Get(*styleFlag).Builder()
|
||||
if *htmlHighlightStyleFlag != "" {
|
||||
builder.Add(chroma.LineHighlight, *htmlHighlightStyleFlag)
|
||||
}
|
||||
if *htmlLinesStyleFlag != "" {
|
||||
builder.Add(chroma.LineNumbers, *htmlLinesStyleFlag)
|
||||
}
|
||||
style, err := builder.Build()
|
||||
kingpin.FatalIfError(err, "")
|
||||
|
||||
if *formatterFlag == "html" {
|
||||
options := []html.Option{
|
||||
html.TabWidth(*htmlTabWidthFlag),
|
||||
html.BaseLineNumber(*htmlBaseLineFlag),
|
||||
}
|
||||
if *htmlPrefixFlag != "" {
|
||||
options = append(options, html.ClassPrefix(*htmlPrefixFlag))
|
||||
}
|
||||
|
||||
// Dump styles.
|
||||
if *htmlStylesFlag {
|
||||
formatter := html.New(html.WithClasses())
|
||||
formatter.WriteCSS(w, style)
|
||||
return
|
||||
}
|
||||
if !*htmlInlineStyleFlag {
|
||||
options = append(options, html.WithClasses())
|
||||
}
|
||||
if !*htmlOnlyFlag {
|
||||
options = append(options, html.Standalone())
|
||||
}
|
||||
if *htmlLinesFlag {
|
||||
options = append(options, html.WithLineNumbers())
|
||||
}
|
||||
if *htmlLinesTableFlag {
|
||||
options = append(options, html.LineNumbersInTable())
|
||||
}
|
||||
if len(*htmlHighlightFlag) > 0 {
|
||||
ranges := [][2]int{}
|
||||
for _, span := range strings.Split(*htmlHighlightFlag, ",") {
|
||||
parts := strings.Split(span, ":")
|
||||
if len(parts) > 2 {
|
||||
kingpin.Fatalf("range should be N[:M], not %q", span)
|
||||
}
|
||||
start, err := strconv.ParseInt(parts[0], 10, 64)
|
||||
kingpin.FatalIfError(err, "min value of range should be integer not %q", parts[0])
|
||||
end := start
|
||||
if len(parts) == 2 {
|
||||
end, err = strconv.ParseInt(parts[1], 10, 64)
|
||||
kingpin.FatalIfError(err, "max value of range should be integer not %q", parts[1])
|
||||
}
|
||||
ranges = append(ranges, [2]int{int(start), int(end)})
|
||||
}
|
||||
options = append(options, html.HighlightLines(ranges))
|
||||
}
|
||||
formatters.Register("html", html.New(options...))
|
||||
}
|
||||
if len(*filesArgs) == 0 {
|
||||
contents, err := ioutil.ReadAll(os.Stdin)
|
||||
kingpin.FatalIfError(err, "")
|
||||
format(w, style, lex(*filenameFlag, string(contents)))
|
||||
} else {
|
||||
for _, filename := range *filesArgs {
|
||||
contents, err := ioutil.ReadFile(filename)
|
||||
kingpin.FatalIfError(err, "")
|
||||
if *checkFlag {
|
||||
check(filename, lex(filename, string(contents)))
|
||||
} else {
|
||||
format(w, style, lex(filename, string(contents)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listAll() {
|
||||
fmt.Println("lexers:")
|
||||
sort.Sort(lexers.Registry.Lexers)
|
||||
for _, l := range lexers.Registry.Lexers {
|
||||
config := l.Config()
|
||||
fmt.Printf(" %s\n", config.Name)
|
||||
filenames := []string{}
|
||||
filenames = append(filenames, config.Filenames...)
|
||||
filenames = append(filenames, config.AliasFilenames...)
|
||||
if len(config.Aliases) > 0 {
|
||||
fmt.Printf(" aliases: %s\n", strings.Join(config.Aliases, " "))
|
||||
}
|
||||
if len(filenames) > 0 {
|
||||
fmt.Printf(" filenames: %s\n", strings.Join(filenames, " "))
|
||||
}
|
||||
if len(config.MimeTypes) > 0 {
|
||||
fmt.Printf(" mimetypes: %s\n", strings.Join(config.MimeTypes, " "))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Printf("styles:")
|
||||
for _, name := range styles.Names() {
|
||||
fmt.Printf(" %s", name)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Printf("formatters:")
|
||||
for _, name := range formatters.Names() {
|
||||
fmt.Printf(" %s", name)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func lex(path string, contents string) chroma.Iterator {
|
||||
lexer := selexer(path, contents)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
if rel, ok := lexer.(*chroma.RegexLexer); ok {
|
||||
rel.Trace(*traceFlag)
|
||||
}
|
||||
lexer = chroma.Coalesce(lexer)
|
||||
it, err := lexer.Tokenise(nil, string(contents))
|
||||
kingpin.FatalIfError(err, "")
|
||||
return it
|
||||
}
|
||||
|
||||
func selexer(path, contents string) (lexer chroma.Lexer) {
|
||||
if *lexerFlag != "" {
|
||||
return lexers.Get(*lexerFlag)
|
||||
}
|
||||
if path != "" {
|
||||
lexer := lexers.Match(path)
|
||||
if lexer != nil {
|
||||
return lexer
|
||||
}
|
||||
}
|
||||
return lexers.Analyse(contents)
|
||||
}
|
||||
|
||||
func format(w io.Writer, style *chroma.Style, it chroma.Iterator) {
|
||||
formatter := formatters.Get(*formatterFlag)
|
||||
err := formatter.Format(w, style, it)
|
||||
kingpin.FatalIfError(err, "")
|
||||
}
|
||||
|
||||
func check(filename string, it chroma.Iterator) {
|
||||
line, col := 1, 0
|
||||
for token := it(); token != nil; token = it() {
|
||||
if token.Type == chroma.Error {
|
||||
fmt.Printf("%s:%d:%d %q\n", filename, line, col, token.String())
|
||||
}
|
||||
for _, c := range token.String() {
|
||||
col++
|
||||
if c == '\n' {
|
||||
line, col = line+1, 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
func TestCoalesce(t *testing.T) {
|
||||
lexer := Coalesce(MustNewLexer(nil, Rules{
|
||||
"root": []Rule{
|
||||
{`[!@#$%^&*()]`, Punctuation, nil},
|
||||
},
|
||||
}))
|
||||
actual, err := Tokenise(lexer, nil, "!@#$")
|
||||
assert.NoError(t, err)
|
||||
expected := []*Token{{Punctuation, "!@#$"}}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
func TestColourRGB(t *testing.T) {
|
||||
colour := ParseColour("#8913af")
|
||||
assert.Equal(t, uint8(0x89), colour.Red())
|
||||
assert.Equal(t, uint8(0x13), colour.Green())
|
||||
assert.Equal(t, uint8(0xaf), colour.Blue())
|
||||
}
|
||||
|
||||
func TestColourString(t *testing.T) {
|
||||
assert.Equal(t, "#8913af", ParseColour("#8913af").String())
|
||||
}
|
||||
|
||||
func distance(a, b uint8) uint8 {
|
||||
if a < b {
|
||||
return b - a
|
||||
}
|
||||
return a - b
|
||||
}
|
||||
|
||||
func TestColourBrighten(t *testing.T) {
|
||||
actual := NewColour(128, 128, 128).Brighten(0.5)
|
||||
// Closeish to what we expect is fine.
|
||||
assert.True(t, distance(192, actual.Red()) <= 2)
|
||||
assert.True(t, distance(192, actual.Blue()) <= 2)
|
||||
assert.True(t, distance(192, actual.Green()) <= 2)
|
||||
actual = NewColour(128, 128, 128).Brighten(-0.5)
|
||||
assert.True(t, distance(65, actual.Red()) <= 2)
|
||||
assert.True(t, distance(65, actual.Blue()) <= 2)
|
||||
assert.True(t, distance(65, actual.Green()) <= 2)
|
||||
}
|
||||
|
||||
func TestColourBrightess(t *testing.T) {
|
||||
actual := NewColour(128, 128, 128).Brightness()
|
||||
assert.True(t, distance(128, uint8(actual*255.0)) <= 2)
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/formatters/html"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoOp formatter.
|
||||
NoOp = Register("noop", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, iterator chroma.Iterator) error {
|
||||
for t := iterator(); t != nil; t = iterator() {
|
||||
if _, err := io.WriteString(w, t.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
// Default HTML formatter outputs self-contained HTML.
|
||||
htmlFull = Register("html", html.New(html.Standalone(), html.WithClasses()))
|
||||
)
|
||||
|
||||
// Fallback formatter.
|
||||
var Fallback = NoOp
|
||||
|
||||
// Registry of Formatters.
|
||||
var Registry = map[string]chroma.Formatter{}
|
||||
|
||||
// Names of registered formatters.
|
||||
func Names() []string {
|
||||
out := []string{}
|
||||
for name := range Registry {
|
||||
out = append(out, name)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Get formatter by name.
|
||||
//
|
||||
// If the given formatter is not found, the Fallback formatter will be returned.
|
||||
func Get(name string) chroma.Formatter {
|
||||
if f, ok := Registry[name]; ok {
|
||||
return f
|
||||
}
|
||||
return Fallback
|
||||
}
|
||||
|
||||
// Register a named formatter.
|
||||
func Register(name string, formatter chroma.Formatter) chroma.Formatter {
|
||||
Registry[name] = formatter
|
||||
return formatter
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package html
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
)
|
||||
|
||||
func TestCompressStyle(t *testing.T) {
|
||||
style := "color: #888888; background-color: #faffff"
|
||||
actual := compressStyle(style)
|
||||
expected := "color:#888;background-color:#faffff"
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func BenchmarkHTMLFormatter(b *testing.B) {
|
||||
formatter := New()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
it, err := lexers.Go.Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
|
||||
assert.NoError(b, err)
|
||||
err = formatter.Format(ioutil.Discard, styles.Fallback, it)
|
||||
assert.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitTokensIntoLines(t *testing.T) {
|
||||
in := []*chroma.Token{
|
||||
{Value: "hello", Type: chroma.NameKeyword},
|
||||
{Value: " world\nwhat?\n", Type: chroma.NameKeyword},
|
||||
}
|
||||
expected := [][]*chroma.Token{
|
||||
{
|
||||
{Type: chroma.NameKeyword, Value: "hello"},
|
||||
{Type: chroma.NameKeyword, Value: " world\n"},
|
||||
},
|
||||
{
|
||||
{Type: chroma.NameKeyword, Value: "what?\n"},
|
||||
},
|
||||
{
|
||||
{Type: chroma.NameKeyword},
|
||||
},
|
||||
}
|
||||
actual := splitTokensIntoLines(in)
|
||||
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)
|
||||
}
|
||||
|
||||
func TestFormatter_styleToCSS(t *testing.T) {
|
||||
builder := styles.Get("github").Builder()
|
||||
builder.Add(chroma.LineHighlight, "bg:#ffffcc")
|
||||
builder.Add(chroma.LineNumbers, "bold")
|
||||
style, err := builder.Build()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
formatter := New(WithClasses())
|
||||
css := formatter.styleToCSS(style)
|
||||
for _, s := range css {
|
||||
if strings.HasPrefix(strings.TrimSpace(s), ";") {
|
||||
t.Errorf("rule starts with semicolon - expected valid css rule without semicolon: %v", s)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// JSON formatter outputs the raw token structures as JSON.
|
||||
var JSON = Register("json", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
|
||||
fmt.Fprintln(w, "[")
|
||||
i := 0
|
||||
for t := it(); t != nil; t = it() {
|
||||
if i > 0 {
|
||||
fmt.Fprintln(w, ",")
|
||||
}
|
||||
i++
|
||||
bytes, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprint(w, " "+string(bytes)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
fmt.Fprintln(w, "]")
|
||||
return nil
|
||||
}))
|
|
@ -1,18 +0,0 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// Tokens formatter outputs the raw token structures.
|
||||
var Tokens = Register("tokens", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
|
||||
for t := it(); t != nil; t = it() {
|
||||
if _, err := fmt.Fprintln(w, t.GoString()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
|
@ -1,250 +0,0 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
type ttyTable struct {
|
||||
foreground map[chroma.Colour]string
|
||||
background map[chroma.Colour]string
|
||||
}
|
||||
|
||||
var c = chroma.MustParseColour
|
||||
|
||||
var ttyTables = map[int]*ttyTable{
|
||||
8: {
|
||||
foreground: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
|
||||
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
|
||||
c("#555555"): "\033[90m", c("#ff0000"): "\033[91m", c("#00ff00"): "\033[92m", c("#ffff00"): "\033[93m",
|
||||
c("#0000ff"): "\033[94m", c("#ff00ff"): "\033[95m", c("#00ffff"): "\033[96m", c("#ffffff"): "\033[97m",
|
||||
},
|
||||
background: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[40m", c("#7f0000"): "\033[41m", c("#007f00"): "\033[42m", c("#7f7fe0"): "\033[43m",
|
||||
c("#00007f"): "\033[44m", c("#7f007f"): "\033[45m", c("#007f7f"): "\033[46m", c("#e5e5e5"): "\033[47m",
|
||||
c("#555555"): "\033[100m", c("#ff0000"): "\033[101m", c("#00ff00"): "\033[102m", c("#ffff00"): "\033[103m",
|
||||
c("#0000ff"): "\033[104m", c("#ff00ff"): "\033[105m", c("#00ffff"): "\033[106m", c("#ffffff"): "\033[107m",
|
||||
},
|
||||
},
|
||||
256: {
|
||||
foreground: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[38;5;0m", c("#800000"): "\033[38;5;1m", c("#008000"): "\033[38;5;2m", c("#808000"): "\033[38;5;3m",
|
||||
c("#000080"): "\033[38;5;4m", c("#800080"): "\033[38;5;5m", c("#008080"): "\033[38;5;6m", c("#c0c0c0"): "\033[38;5;7m",
|
||||
c("#808080"): "\033[38;5;8m", c("#ff0000"): "\033[38;5;9m", c("#00ff00"): "\033[38;5;10m", c("#ffff00"): "\033[38;5;11m",
|
||||
c("#0000ff"): "\033[38;5;12m", c("#ff00ff"): "\033[38;5;13m", c("#00ffff"): "\033[38;5;14m", c("#ffffff"): "\033[38;5;15m",
|
||||
c("#000000"): "\033[38;5;16m", c("#00005f"): "\033[38;5;17m", c("#000087"): "\033[38;5;18m", c("#0000af"): "\033[38;5;19m",
|
||||
c("#0000d7"): "\033[38;5;20m", c("#0000ff"): "\033[38;5;21m", c("#005f00"): "\033[38;5;22m", c("#005f5f"): "\033[38;5;23m",
|
||||
c("#005f87"): "\033[38;5;24m", c("#005faf"): "\033[38;5;25m", c("#005fd7"): "\033[38;5;26m", c("#005fff"): "\033[38;5;27m",
|
||||
c("#008700"): "\033[38;5;28m", c("#00875f"): "\033[38;5;29m", c("#008787"): "\033[38;5;30m", c("#0087af"): "\033[38;5;31m",
|
||||
c("#0087d7"): "\033[38;5;32m", c("#0087ff"): "\033[38;5;33m", c("#00af00"): "\033[38;5;34m", c("#00af5f"): "\033[38;5;35m",
|
||||
c("#00af87"): "\033[38;5;36m", c("#00afaf"): "\033[38;5;37m", c("#00afd7"): "\033[38;5;38m", c("#00afff"): "\033[38;5;39m",
|
||||
c("#00d700"): "\033[38;5;40m", c("#00d75f"): "\033[38;5;41m", c("#00d787"): "\033[38;5;42m", c("#00d7af"): "\033[38;5;43m",
|
||||
c("#00d7d7"): "\033[38;5;44m", c("#00d7ff"): "\033[38;5;45m", c("#00ff00"): "\033[38;5;46m", c("#00ff5f"): "\033[38;5;47m",
|
||||
c("#00ff87"): "\033[38;5;48m", c("#00ffaf"): "\033[38;5;49m", c("#00ffd7"): "\033[38;5;50m", c("#00ffff"): "\033[38;5;51m",
|
||||
c("#5f0000"): "\033[38;5;52m", c("#5f005f"): "\033[38;5;53m", c("#5f0087"): "\033[38;5;54m", c("#5f00af"): "\033[38;5;55m",
|
||||
c("#5f00d7"): "\033[38;5;56m", c("#5f00ff"): "\033[38;5;57m", c("#5f5f00"): "\033[38;5;58m", c("#5f5f5f"): "\033[38;5;59m",
|
||||
c("#5f5f87"): "\033[38;5;60m", c("#5f5faf"): "\033[38;5;61m", c("#5f5fd7"): "\033[38;5;62m", c("#5f5fff"): "\033[38;5;63m",
|
||||
c("#5f8700"): "\033[38;5;64m", c("#5f875f"): "\033[38;5;65m", c("#5f8787"): "\033[38;5;66m", c("#5f87af"): "\033[38;5;67m",
|
||||
c("#5f87d7"): "\033[38;5;68m", c("#5f87ff"): "\033[38;5;69m", c("#5faf00"): "\033[38;5;70m", c("#5faf5f"): "\033[38;5;71m",
|
||||
c("#5faf87"): "\033[38;5;72m", c("#5fafaf"): "\033[38;5;73m", c("#5fafd7"): "\033[38;5;74m", c("#5fafff"): "\033[38;5;75m",
|
||||
c("#5fd700"): "\033[38;5;76m", c("#5fd75f"): "\033[38;5;77m", c("#5fd787"): "\033[38;5;78m", c("#5fd7af"): "\033[38;5;79m",
|
||||
c("#5fd7d7"): "\033[38;5;80m", c("#5fd7ff"): "\033[38;5;81m", c("#5fff00"): "\033[38;5;82m", c("#5fff5f"): "\033[38;5;83m",
|
||||
c("#5fff87"): "\033[38;5;84m", c("#5fffaf"): "\033[38;5;85m", c("#5fffd7"): "\033[38;5;86m", c("#5fffff"): "\033[38;5;87m",
|
||||
c("#870000"): "\033[38;5;88m", c("#87005f"): "\033[38;5;89m", c("#870087"): "\033[38;5;90m", c("#8700af"): "\033[38;5;91m",
|
||||
c("#8700d7"): "\033[38;5;92m", c("#8700ff"): "\033[38;5;93m", c("#875f00"): "\033[38;5;94m", c("#875f5f"): "\033[38;5;95m",
|
||||
c("#875f87"): "\033[38;5;96m", c("#875faf"): "\033[38;5;97m", c("#875fd7"): "\033[38;5;98m", c("#875fff"): "\033[38;5;99m",
|
||||
c("#878700"): "\033[38;5;100m", c("#87875f"): "\033[38;5;101m", c("#878787"): "\033[38;5;102m", c("#8787af"): "\033[38;5;103m",
|
||||
c("#8787d7"): "\033[38;5;104m", c("#8787ff"): "\033[38;5;105m", c("#87af00"): "\033[38;5;106m", c("#87af5f"): "\033[38;5;107m",
|
||||
c("#87af87"): "\033[38;5;108m", c("#87afaf"): "\033[38;5;109m", c("#87afd7"): "\033[38;5;110m", c("#87afff"): "\033[38;5;111m",
|
||||
c("#87d700"): "\033[38;5;112m", c("#87d75f"): "\033[38;5;113m", c("#87d787"): "\033[38;5;114m", c("#87d7af"): "\033[38;5;115m",
|
||||
c("#87d7d7"): "\033[38;5;116m", c("#87d7ff"): "\033[38;5;117m", c("#87ff00"): "\033[38;5;118m", c("#87ff5f"): "\033[38;5;119m",
|
||||
c("#87ff87"): "\033[38;5;120m", c("#87ffaf"): "\033[38;5;121m", c("#87ffd7"): "\033[38;5;122m", c("#87ffff"): "\033[38;5;123m",
|
||||
c("#af0000"): "\033[38;5;124m", c("#af005f"): "\033[38;5;125m", c("#af0087"): "\033[38;5;126m", c("#af00af"): "\033[38;5;127m",
|
||||
c("#af00d7"): "\033[38;5;128m", c("#af00ff"): "\033[38;5;129m", c("#af5f00"): "\033[38;5;130m", c("#af5f5f"): "\033[38;5;131m",
|
||||
c("#af5f87"): "\033[38;5;132m", c("#af5faf"): "\033[38;5;133m", c("#af5fd7"): "\033[38;5;134m", c("#af5fff"): "\033[38;5;135m",
|
||||
c("#af8700"): "\033[38;5;136m", c("#af875f"): "\033[38;5;137m", c("#af8787"): "\033[38;5;138m", c("#af87af"): "\033[38;5;139m",
|
||||
c("#af87d7"): "\033[38;5;140m", c("#af87ff"): "\033[38;5;141m", c("#afaf00"): "\033[38;5;142m", c("#afaf5f"): "\033[38;5;143m",
|
||||
c("#afaf87"): "\033[38;5;144m", c("#afafaf"): "\033[38;5;145m", c("#afafd7"): "\033[38;5;146m", c("#afafff"): "\033[38;5;147m",
|
||||
c("#afd700"): "\033[38;5;148m", c("#afd75f"): "\033[38;5;149m", c("#afd787"): "\033[38;5;150m", c("#afd7af"): "\033[38;5;151m",
|
||||
c("#afd7d7"): "\033[38;5;152m", c("#afd7ff"): "\033[38;5;153m", c("#afff00"): "\033[38;5;154m", c("#afff5f"): "\033[38;5;155m",
|
||||
c("#afff87"): "\033[38;5;156m", c("#afffaf"): "\033[38;5;157m", c("#afffd7"): "\033[38;5;158m", c("#afffff"): "\033[38;5;159m",
|
||||
c("#d70000"): "\033[38;5;160m", c("#d7005f"): "\033[38;5;161m", c("#d70087"): "\033[38;5;162m", c("#d700af"): "\033[38;5;163m",
|
||||
c("#d700d7"): "\033[38;5;164m", c("#d700ff"): "\033[38;5;165m", c("#d75f00"): "\033[38;5;166m", c("#d75f5f"): "\033[38;5;167m",
|
||||
c("#d75f87"): "\033[38;5;168m", c("#d75faf"): "\033[38;5;169m", c("#d75fd7"): "\033[38;5;170m", c("#d75fff"): "\033[38;5;171m",
|
||||
c("#d78700"): "\033[38;5;172m", c("#d7875f"): "\033[38;5;173m", c("#d78787"): "\033[38;5;174m", c("#d787af"): "\033[38;5;175m",
|
||||
c("#d787d7"): "\033[38;5;176m", c("#d787ff"): "\033[38;5;177m", c("#d7af00"): "\033[38;5;178m", c("#d7af5f"): "\033[38;5;179m",
|
||||
c("#d7af87"): "\033[38;5;180m", c("#d7afaf"): "\033[38;5;181m", c("#d7afd7"): "\033[38;5;182m", c("#d7afff"): "\033[38;5;183m",
|
||||
c("#d7d700"): "\033[38;5;184m", c("#d7d75f"): "\033[38;5;185m", c("#d7d787"): "\033[38;5;186m", c("#d7d7af"): "\033[38;5;187m",
|
||||
c("#d7d7d7"): "\033[38;5;188m", c("#d7d7ff"): "\033[38;5;189m", c("#d7ff00"): "\033[38;5;190m", c("#d7ff5f"): "\033[38;5;191m",
|
||||
c("#d7ff87"): "\033[38;5;192m", c("#d7ffaf"): "\033[38;5;193m", c("#d7ffd7"): "\033[38;5;194m", c("#d7ffff"): "\033[38;5;195m",
|
||||
c("#ff0000"): "\033[38;5;196m", c("#ff005f"): "\033[38;5;197m", c("#ff0087"): "\033[38;5;198m", c("#ff00af"): "\033[38;5;199m",
|
||||
c("#ff00d7"): "\033[38;5;200m", c("#ff00ff"): "\033[38;5;201m", c("#ff5f00"): "\033[38;5;202m", c("#ff5f5f"): "\033[38;5;203m",
|
||||
c("#ff5f87"): "\033[38;5;204m", c("#ff5faf"): "\033[38;5;205m", c("#ff5fd7"): "\033[38;5;206m", c("#ff5fff"): "\033[38;5;207m",
|
||||
c("#ff8700"): "\033[38;5;208m", c("#ff875f"): "\033[38;5;209m", c("#ff8787"): "\033[38;5;210m", c("#ff87af"): "\033[38;5;211m",
|
||||
c("#ff87d7"): "\033[38;5;212m", c("#ff87ff"): "\033[38;5;213m", c("#ffaf00"): "\033[38;5;214m", c("#ffaf5f"): "\033[38;5;215m",
|
||||
c("#ffaf87"): "\033[38;5;216m", c("#ffafaf"): "\033[38;5;217m", c("#ffafd7"): "\033[38;5;218m", c("#ffafff"): "\033[38;5;219m",
|
||||
c("#ffd700"): "\033[38;5;220m", c("#ffd75f"): "\033[38;5;221m", c("#ffd787"): "\033[38;5;222m", c("#ffd7af"): "\033[38;5;223m",
|
||||
c("#ffd7d7"): "\033[38;5;224m", c("#ffd7ff"): "\033[38;5;225m", c("#ffff00"): "\033[38;5;226m", c("#ffff5f"): "\033[38;5;227m",
|
||||
c("#ffff87"): "\033[38;5;228m", c("#ffffaf"): "\033[38;5;229m", c("#ffffd7"): "\033[38;5;230m", c("#ffffff"): "\033[38;5;231m",
|
||||
c("#080808"): "\033[38;5;232m", c("#121212"): "\033[38;5;233m", c("#1c1c1c"): "\033[38;5;234m", c("#262626"): "\033[38;5;235m",
|
||||
c("#303030"): "\033[38;5;236m", c("#3a3a3a"): "\033[38;5;237m", c("#444444"): "\033[38;5;238m", c("#4e4e4e"): "\033[38;5;239m",
|
||||
c("#585858"): "\033[38;5;240m", c("#626262"): "\033[38;5;241m", c("#6c6c6c"): "\033[38;5;242m", c("#767676"): "\033[38;5;243m",
|
||||
c("#808080"): "\033[38;5;244m", c("#8a8a8a"): "\033[38;5;245m", c("#949494"): "\033[38;5;246m", c("#9e9e9e"): "\033[38;5;247m",
|
||||
c("#a8a8a8"): "\033[38;5;248m", c("#b2b2b2"): "\033[38;5;249m", c("#bcbcbc"): "\033[38;5;250m", c("#c6c6c6"): "\033[38;5;251m",
|
||||
c("#d0d0d0"): "\033[38;5;252m", c("#dadada"): "\033[38;5;253m", c("#e4e4e4"): "\033[38;5;254m", c("#eeeeee"): "\033[38;5;255m",
|
||||
},
|
||||
background: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[48;5;0m", c("#800000"): "\033[48;5;1m", c("#008000"): "\033[48;5;2m", c("#808000"): "\033[48;5;3m",
|
||||
c("#000080"): "\033[48;5;4m", c("#800080"): "\033[48;5;5m", c("#008080"): "\033[48;5;6m", c("#c0c0c0"): "\033[48;5;7m",
|
||||
c("#808080"): "\033[48;5;8m", c("#ff0000"): "\033[48;5;9m", c("#00ff00"): "\033[48;5;10m", c("#ffff00"): "\033[48;5;11m",
|
||||
c("#0000ff"): "\033[48;5;12m", c("#ff00ff"): "\033[48;5;13m", c("#00ffff"): "\033[48;5;14m", c("#ffffff"): "\033[48;5;15m",
|
||||
c("#000000"): "\033[48;5;16m", c("#00005f"): "\033[48;5;17m", c("#000087"): "\033[48;5;18m", c("#0000af"): "\033[48;5;19m",
|
||||
c("#0000d7"): "\033[48;5;20m", c("#0000ff"): "\033[48;5;21m", c("#005f00"): "\033[48;5;22m", c("#005f5f"): "\033[48;5;23m",
|
||||
c("#005f87"): "\033[48;5;24m", c("#005faf"): "\033[48;5;25m", c("#005fd7"): "\033[48;5;26m", c("#005fff"): "\033[48;5;27m",
|
||||
c("#008700"): "\033[48;5;28m", c("#00875f"): "\033[48;5;29m", c("#008787"): "\033[48;5;30m", c("#0087af"): "\033[48;5;31m",
|
||||
c("#0087d7"): "\033[48;5;32m", c("#0087ff"): "\033[48;5;33m", c("#00af00"): "\033[48;5;34m", c("#00af5f"): "\033[48;5;35m",
|
||||
c("#00af87"): "\033[48;5;36m", c("#00afaf"): "\033[48;5;37m", c("#00afd7"): "\033[48;5;38m", c("#00afff"): "\033[48;5;39m",
|
||||
c("#00d700"): "\033[48;5;40m", c("#00d75f"): "\033[48;5;41m", c("#00d787"): "\033[48;5;42m", c("#00d7af"): "\033[48;5;43m",
|
||||
c("#00d7d7"): "\033[48;5;44m", c("#00d7ff"): "\033[48;5;45m", c("#00ff00"): "\033[48;5;46m", c("#00ff5f"): "\033[48;5;47m",
|
||||
c("#00ff87"): "\033[48;5;48m", c("#00ffaf"): "\033[48;5;49m", c("#00ffd7"): "\033[48;5;50m", c("#00ffff"): "\033[48;5;51m",
|
||||
c("#5f0000"): "\033[48;5;52m", c("#5f005f"): "\033[48;5;53m", c("#5f0087"): "\033[48;5;54m", c("#5f00af"): "\033[48;5;55m",
|
||||
c("#5f00d7"): "\033[48;5;56m", c("#5f00ff"): "\033[48;5;57m", c("#5f5f00"): "\033[48;5;58m", c("#5f5f5f"): "\033[48;5;59m",
|
||||
c("#5f5f87"): "\033[48;5;60m", c("#5f5faf"): "\033[48;5;61m", c("#5f5fd7"): "\033[48;5;62m", c("#5f5fff"): "\033[48;5;63m",
|
||||
c("#5f8700"): "\033[48;5;64m", c("#5f875f"): "\033[48;5;65m", c("#5f8787"): "\033[48;5;66m", c("#5f87af"): "\033[48;5;67m",
|
||||
c("#5f87d7"): "\033[48;5;68m", c("#5f87ff"): "\033[48;5;69m", c("#5faf00"): "\033[48;5;70m", c("#5faf5f"): "\033[48;5;71m",
|
||||
c("#5faf87"): "\033[48;5;72m", c("#5fafaf"): "\033[48;5;73m", c("#5fafd7"): "\033[48;5;74m", c("#5fafff"): "\033[48;5;75m",
|
||||
c("#5fd700"): "\033[48;5;76m", c("#5fd75f"): "\033[48;5;77m", c("#5fd787"): "\033[48;5;78m", c("#5fd7af"): "\033[48;5;79m",
|
||||
c("#5fd7d7"): "\033[48;5;80m", c("#5fd7ff"): "\033[48;5;81m", c("#5fff00"): "\033[48;5;82m", c("#5fff5f"): "\033[48;5;83m",
|
||||
c("#5fff87"): "\033[48;5;84m", c("#5fffaf"): "\033[48;5;85m", c("#5fffd7"): "\033[48;5;86m", c("#5fffff"): "\033[48;5;87m",
|
||||
c("#870000"): "\033[48;5;88m", c("#87005f"): "\033[48;5;89m", c("#870087"): "\033[48;5;90m", c("#8700af"): "\033[48;5;91m",
|
||||
c("#8700d7"): "\033[48;5;92m", c("#8700ff"): "\033[48;5;93m", c("#875f00"): "\033[48;5;94m", c("#875f5f"): "\033[48;5;95m",
|
||||
c("#875f87"): "\033[48;5;96m", c("#875faf"): "\033[48;5;97m", c("#875fd7"): "\033[48;5;98m", c("#875fff"): "\033[48;5;99m",
|
||||
c("#878700"): "\033[48;5;100m", c("#87875f"): "\033[48;5;101m", c("#878787"): "\033[48;5;102m", c("#8787af"): "\033[48;5;103m",
|
||||
c("#8787d7"): "\033[48;5;104m", c("#8787ff"): "\033[48;5;105m", c("#87af00"): "\033[48;5;106m", c("#87af5f"): "\033[48;5;107m",
|
||||
c("#87af87"): "\033[48;5;108m", c("#87afaf"): "\033[48;5;109m", c("#87afd7"): "\033[48;5;110m", c("#87afff"): "\033[48;5;111m",
|
||||
c("#87d700"): "\033[48;5;112m", c("#87d75f"): "\033[48;5;113m", c("#87d787"): "\033[48;5;114m", c("#87d7af"): "\033[48;5;115m",
|
||||
c("#87d7d7"): "\033[48;5;116m", c("#87d7ff"): "\033[48;5;117m", c("#87ff00"): "\033[48;5;118m", c("#87ff5f"): "\033[48;5;119m",
|
||||
c("#87ff87"): "\033[48;5;120m", c("#87ffaf"): "\033[48;5;121m", c("#87ffd7"): "\033[48;5;122m", c("#87ffff"): "\033[48;5;123m",
|
||||
c("#af0000"): "\033[48;5;124m", c("#af005f"): "\033[48;5;125m", c("#af0087"): "\033[48;5;126m", c("#af00af"): "\033[48;5;127m",
|
||||
c("#af00d7"): "\033[48;5;128m", c("#af00ff"): "\033[48;5;129m", c("#af5f00"): "\033[48;5;130m", c("#af5f5f"): "\033[48;5;131m",
|
||||
c("#af5f87"): "\033[48;5;132m", c("#af5faf"): "\033[48;5;133m", c("#af5fd7"): "\033[48;5;134m", c("#af5fff"): "\033[48;5;135m",
|
||||
c("#af8700"): "\033[48;5;136m", c("#af875f"): "\033[48;5;137m", c("#af8787"): "\033[48;5;138m", c("#af87af"): "\033[48;5;139m",
|
||||
c("#af87d7"): "\033[48;5;140m", c("#af87ff"): "\033[48;5;141m", c("#afaf00"): "\033[48;5;142m", c("#afaf5f"): "\033[48;5;143m",
|
||||
c("#afaf87"): "\033[48;5;144m", c("#afafaf"): "\033[48;5;145m", c("#afafd7"): "\033[48;5;146m", c("#afafff"): "\033[48;5;147m",
|
||||
c("#afd700"): "\033[48;5;148m", c("#afd75f"): "\033[48;5;149m", c("#afd787"): "\033[48;5;150m", c("#afd7af"): "\033[48;5;151m",
|
||||
c("#afd7d7"): "\033[48;5;152m", c("#afd7ff"): "\033[48;5;153m", c("#afff00"): "\033[48;5;154m", c("#afff5f"): "\033[48;5;155m",
|
||||
c("#afff87"): "\033[48;5;156m", c("#afffaf"): "\033[48;5;157m", c("#afffd7"): "\033[48;5;158m", c("#afffff"): "\033[48;5;159m",
|
||||
c("#d70000"): "\033[48;5;160m", c("#d7005f"): "\033[48;5;161m", c("#d70087"): "\033[48;5;162m", c("#d700af"): "\033[48;5;163m",
|
||||
c("#d700d7"): "\033[48;5;164m", c("#d700ff"): "\033[48;5;165m", c("#d75f00"): "\033[48;5;166m", c("#d75f5f"): "\033[48;5;167m",
|
||||
c("#d75f87"): "\033[48;5;168m", c("#d75faf"): "\033[48;5;169m", c("#d75fd7"): "\033[48;5;170m", c("#d75fff"): "\033[48;5;171m",
|
||||
c("#d78700"): "\033[48;5;172m", c("#d7875f"): "\033[48;5;173m", c("#d78787"): "\033[48;5;174m", c("#d787af"): "\033[48;5;175m",
|
||||
c("#d787d7"): "\033[48;5;176m", c("#d787ff"): "\033[48;5;177m", c("#d7af00"): "\033[48;5;178m", c("#d7af5f"): "\033[48;5;179m",
|
||||
c("#d7af87"): "\033[48;5;180m", c("#d7afaf"): "\033[48;5;181m", c("#d7afd7"): "\033[48;5;182m", c("#d7afff"): "\033[48;5;183m",
|
||||
c("#d7d700"): "\033[48;5;184m", c("#d7d75f"): "\033[48;5;185m", c("#d7d787"): "\033[48;5;186m", c("#d7d7af"): "\033[48;5;187m",
|
||||
c("#d7d7d7"): "\033[48;5;188m", c("#d7d7ff"): "\033[48;5;189m", c("#d7ff00"): "\033[48;5;190m", c("#d7ff5f"): "\033[48;5;191m",
|
||||
c("#d7ff87"): "\033[48;5;192m", c("#d7ffaf"): "\033[48;5;193m", c("#d7ffd7"): "\033[48;5;194m", c("#d7ffff"): "\033[48;5;195m",
|
||||
c("#ff0000"): "\033[48;5;196m", c("#ff005f"): "\033[48;5;197m", c("#ff0087"): "\033[48;5;198m", c("#ff00af"): "\033[48;5;199m",
|
||||
c("#ff00d7"): "\033[48;5;200m", c("#ff00ff"): "\033[48;5;201m", c("#ff5f00"): "\033[48;5;202m", c("#ff5f5f"): "\033[48;5;203m",
|
||||
c("#ff5f87"): "\033[48;5;204m", c("#ff5faf"): "\033[48;5;205m", c("#ff5fd7"): "\033[48;5;206m", c("#ff5fff"): "\033[48;5;207m",
|
||||
c("#ff8700"): "\033[48;5;208m", c("#ff875f"): "\033[48;5;209m", c("#ff8787"): "\033[48;5;210m", c("#ff87af"): "\033[48;5;211m",
|
||||
c("#ff87d7"): "\033[48;5;212m", c("#ff87ff"): "\033[48;5;213m", c("#ffaf00"): "\033[48;5;214m", c("#ffaf5f"): "\033[48;5;215m",
|
||||
c("#ffaf87"): "\033[48;5;216m", c("#ffafaf"): "\033[48;5;217m", c("#ffafd7"): "\033[48;5;218m", c("#ffafff"): "\033[48;5;219m",
|
||||
c("#ffd700"): "\033[48;5;220m", c("#ffd75f"): "\033[48;5;221m", c("#ffd787"): "\033[48;5;222m", c("#ffd7af"): "\033[48;5;223m",
|
||||
c("#ffd7d7"): "\033[48;5;224m", c("#ffd7ff"): "\033[48;5;225m", c("#ffff00"): "\033[48;5;226m", c("#ffff5f"): "\033[48;5;227m",
|
||||
c("#ffff87"): "\033[48;5;228m", c("#ffffaf"): "\033[48;5;229m", c("#ffffd7"): "\033[48;5;230m", c("#ffffff"): "\033[48;5;231m",
|
||||
c("#080808"): "\033[48;5;232m", c("#121212"): "\033[48;5;233m", c("#1c1c1c"): "\033[48;5;234m", c("#262626"): "\033[48;5;235m",
|
||||
c("#303030"): "\033[48;5;236m", c("#3a3a3a"): "\033[48;5;237m", c("#444444"): "\033[48;5;238m", c("#4e4e4e"): "\033[48;5;239m",
|
||||
c("#585858"): "\033[48;5;240m", c("#626262"): "\033[48;5;241m", c("#6c6c6c"): "\033[48;5;242m", c("#767676"): "\033[48;5;243m",
|
||||
c("#808080"): "\033[48;5;244m", c("#8a8a8a"): "\033[48;5;245m", c("#949494"): "\033[48;5;246m", c("#9e9e9e"): "\033[48;5;247m",
|
||||
c("#a8a8a8"): "\033[48;5;248m", c("#b2b2b2"): "\033[48;5;249m", c("#bcbcbc"): "\033[48;5;250m", c("#c6c6c6"): "\033[48;5;251m",
|
||||
c("#d0d0d0"): "\033[48;5;252m", c("#dadada"): "\033[48;5;253m", c("#e4e4e4"): "\033[48;5;254m", c("#eeeeee"): "\033[48;5;255m",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func entryToEscapeSequence(table *ttyTable, entry chroma.StyleEntry) string {
|
||||
out := ""
|
||||
if entry.Bold == chroma.Yes {
|
||||
out += "\033[1m"
|
||||
}
|
||||
if entry.Underline == chroma.Yes {
|
||||
out += "\033[4m"
|
||||
}
|
||||
if entry.Colour.IsSet() {
|
||||
out += table.foreground[findClosest(table, entry.Colour)]
|
||||
}
|
||||
if entry.Background.IsSet() {
|
||||
out += table.background[findClosest(table, entry.Background)]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func findClosest(table *ttyTable, seeking chroma.Colour) chroma.Colour {
|
||||
closestColour := chroma.Colour(0)
|
||||
closest := float64(math.MaxFloat64)
|
||||
for colour := range table.foreground {
|
||||
distance := colour.Distance(seeking)
|
||||
if distance < closest {
|
||||
closest = distance
|
||||
closestColour = colour
|
||||
}
|
||||
}
|
||||
return closestColour
|
||||
}
|
||||
|
||||
func styleToEscapeSequence(table *ttyTable, style *chroma.Style) map[chroma.TokenType]string {
|
||||
out := map[chroma.TokenType]string{}
|
||||
for _, ttype := range style.Types() {
|
||||
entry := style.Get(ttype)
|
||||
out[ttype] = entryToEscapeSequence(table, entry)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type indexedTTYFormatter struct {
|
||||
table *ttyTable
|
||||
}
|
||||
|
||||
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)
|
||||
for token := it(); token != nil; token = it() {
|
||||
// TODO: Cache token lookups?
|
||||
clr, ok := theme[token.Type]
|
||||
if !ok {
|
||||
clr, ok = theme[token.Type.SubCategory()]
|
||||
if !ok {
|
||||
clr = theme[token.Type.Category()]
|
||||
// if !ok {
|
||||
// clr = theme[chroma.InheritStyle]
|
||||
// }
|
||||
}
|
||||
}
|
||||
if clr != "" {
|
||||
fmt.Fprint(w, clr)
|
||||
}
|
||||
fmt.Fprint(w, token.Value)
|
||||
if clr != "" {
|
||||
fmt.Fprintf(w, "\033[0m")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TTY8 is an 8-colour terminal formatter.
|
||||
//
|
||||
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||
var TTY8 = Register("terminal", &indexedTTYFormatter{ttyTables[8]})
|
||||
|
||||
// TTY256 is a 256-colour terminal formatter.
|
||||
//
|
||||
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||
var TTY256 = Register("terminal256", &indexedTTYFormatter{ttyTables[256]})
|
|
@ -1,38 +0,0 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// TTY16m is a true-colour terminal formatter.
|
||||
var TTY16m = Register("terminal16m", chroma.FormatterFunc(trueColourFormatter))
|
||||
|
||||
func trueColourFormatter(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
|
||||
for token := it(); token != nil; token = it() {
|
||||
entry := style.Get(token.Type)
|
||||
if !entry.IsZero() {
|
||||
out := ""
|
||||
if entry.Bold == chroma.Yes {
|
||||
out += "\033[1m"
|
||||
}
|
||||
if entry.Underline == chroma.Yes {
|
||||
out += "\033[4m"
|
||||
}
|
||||
if entry.Colour.IsSet() {
|
||||
out += fmt.Sprintf("\033[38;2;%d;%d;%dm", entry.Colour.Red(), entry.Colour.Green(), entry.Colour.Blue())
|
||||
}
|
||||
if entry.Background.IsSet() {
|
||||
out += fmt.Sprintf("\033[48;2;%d;%d;%dm", entry.Background.Red(), entry.Background.Green(), entry.Background.Blue())
|
||||
}
|
||||
fmt.Fprint(w, out)
|
||||
}
|
||||
fmt.Fprint(w, token.Value)
|
||||
if !entry.IsZero() {
|
||||
fmt.Fprint(w, "\033[0m")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
func TestTokenTypeClassifiers(t *testing.T) {
|
||||
assert.True(t, GenericDeleted.InCategory(Generic))
|
||||
assert.True(t, LiteralStringBacktick.InSubCategory(String))
|
||||
assert.Equal(t, LiteralStringBacktick.String(), "LiteralStringBacktick")
|
||||
}
|
||||
|
||||
func TestSimpleLexer(t *testing.T) {
|
||||
lexer, err := NewLexer(
|
||||
&Config{
|
||||
Name: "INI",
|
||||
Aliases: []string{"ini", "cfg"},
|
||||
Filenames: []string{"*.ini", "*.cfg"},
|
||||
},
|
||||
map[string][]Rule{
|
||||
"root": {
|
||||
{`\s+`, Whitespace, nil},
|
||||
{`;.*?$`, Comment, nil},
|
||||
{`\[.*?\]$`, Keyword, nil},
|
||||
{`(.*?)(\s*)(=)(\s*)(.*?)$`, ByGroups(Name, Whitespace, Operator, Whitespace, String), nil},
|
||||
},
|
||||
},
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
actual, err := Tokenise(lexer, nil, `
|
||||
; this is a comment
|
||||
[section]
|
||||
a = 10
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
expected := []*Token{
|
||||
{Whitespace, "\n\t"},
|
||||
{Comment, "; this is a comment"},
|
||||
{Whitespace, "\n\t"},
|
||||
{Keyword, "[section]"},
|
||||
{Whitespace, "\n\t"},
|
||||
{Name, "a"},
|
||||
{Whitespace, " "},
|
||||
{Operator, "="},
|
||||
{Whitespace, " "},
|
||||
{LiteralString, "10"},
|
||||
{Whitespace, "\n"},
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package lexers_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
)
|
||||
|
||||
func TestCompileAllRegexes(t *testing.T) {
|
||||
for _, lexer := range lexers.Registry.Lexers {
|
||||
it, err := lexer.Tokenise(nil, "")
|
||||
assert.NoError(t, err, "%s failed", lexer.Config().Name)
|
||||
err = formatters.NoOp.Format(ioutil.Discard, styles.SwapOff, it)
|
||||
assert.NoError(t, err, "%s failed", lexer.Config().Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Run("ByName", func(t *testing.T) {
|
||||
assert.Equal(t, lexers.Get("xml"), lexers.XML)
|
||||
})
|
||||
t.Run("ByAlias", func(t *testing.T) {
|
||||
assert.Equal(t, lexers.Get("as"), lexers.Actionscript)
|
||||
})
|
||||
t.Run("ViaFilename", func(t *testing.T) {
|
||||
assert.Equal(t, lexers.Get("svg"), lexers.XML)
|
||||
})
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package lexers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
func TestDiffLexerWithoutTralingNewLine(t *testing.T) {
|
||||
diffLexer := Get("diff")
|
||||
it, err := diffLexer.Tokenise(nil, "-foo\n+bar")
|
||||
assert.NoError(t, err)
|
||||
actual := it.Tokens()
|
||||
expected := []*chroma.Token{
|
||||
&chroma.Token{chroma.GenericDeleted, "-foo\n"},
|
||||
&chroma.Token{chroma.GenericInserted, "+bar\n"},
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package lexers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
const lexerBenchSource = `package chroma
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// A Formatter for Chroma lexers.
|
||||
type Formatter interface {
|
||||
// Format returns a formatting function for tokens.
|
||||
Format(w io.Writer, style *Style) (func(*Token), error)
|
||||
}
|
||||
|
||||
// A FormatterFunc is a Formatter implemented as a function.
|
||||
type FormatterFunc func(io.Writer, *Style) (func(*Token), error)
|
||||
|
||||
func (f FormatterFunc) Format(w io.Writer, s *Style) (func(*Token), error) {
|
||||
return f(w, s)
|
||||
}
|
||||
`
|
||||
|
||||
func Benchmark(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
it, err := Go.Tokenise(nil, lexerBenchSource)
|
||||
assert.NoError(b, err)
|
||||
for t := it(); t != nil; t = it() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package lexers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// Test source files are in the form <key>.<key> and validation data is in the form <key>.<key>.expected.
|
||||
func TestLexers(t *testing.T) {
|
||||
files, err := ioutil.ReadDir("testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, file := range files {
|
||||
ext := filepath.Ext(file.Name())[1:]
|
||||
if ext != "actual" {
|
||||
continue
|
||||
}
|
||||
|
||||
lexer := Get(strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())))
|
||||
if !assert.NotNil(t, lexer) {
|
||||
continue
|
||||
}
|
||||
|
||||
filename := filepath.Join("testdata", file.Name())
|
||||
expectedFilename := strings.TrimSuffix(filename, filepath.Ext(filename)) + ".expected"
|
||||
|
||||
lexer = chroma.Coalesce(lexer)
|
||||
t.Run(lexer.Config().Name, func(t *testing.T) {
|
||||
// Read and tokenise source text.
|
||||
actualText, err := ioutil.ReadFile(filename)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
actual, err := chroma.Tokenise(lexer, nil, string(actualText))
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Read expected JSON into token slice.
|
||||
expected := []*chroma.Token{}
|
||||
r, err := os.Open(expectedFilename)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
err = json.NewDecoder(r).Decode(&expected)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Equal?
|
||||
assert.Equal(t, expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
# Lexer tests
|
||||
|
||||
This directory contains input source and expected output lexer tokens.
|
||||
|
||||
Input filenames for lexers are in the form `<name>.actual`. Expected output filenames are in the form `<name>.expected`.
|
||||
|
||||
Each input filename is parsed by the corresponding lexer and checked against the expected JSON-encoded token list.
|
||||
|
||||
|
||||
To add/update tests do the following:
|
||||
|
||||
1. `export LEXER=csharp`
|
||||
1. Create/edit a file `lexers/testdata/${LEXER}.actual` (eg. `csharp.actual`).
|
||||
2. Run `go run ./cmd/chroma/main.go --lexer ${LEXER} --json lexers/testdata/${LEXER}.actual > lexers/testdata/${LEXER}.expected`.
|
||||
3. Run `go test -v -run TestLexers ./lexers`.
|
||||
|
||||
|
||||
eg.
|
||||
|
||||
```bash
|
||||
$ export LEXER=csharp
|
||||
$ go run ./cmd/chroma/main.go --lexer ${LEXER} --json lexers/testdata/${LEXER}.${LEXER} > lexers/testdata/${LEXER}.expected
|
||||
$ go test -v -run TestLexers ./lexers
|
||||
=== RUN TestLexers
|
||||
=== RUN TestLexers/C#
|
||||
=== RUN TestLexers/CSS
|
||||
--- PASS: TestLexers (0.01s)
|
||||
--- PASS: TestLexers/C# (0.00s)
|
||||
--- PASS: TestLexers/CSS (0.00s)
|
||||
PASS
|
||||
ok github.com/alecthomas/chroma/lexers 0.032s
|
||||
```
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
DriveInfo[] drives = DriveInfo.GetDrives();
|
||||
foreach (DriveInfo drive in drives)
|
||||
{
|
||||
IEnumerable<string> driveFolders =
|
||||
Directory.EnumerateDirectories(drive.RootDirectory.ToString());
|
||||
|
||||
foreach (string dir in driveFolders)
|
||||
{
|
||||
Console.WriteLine(dir);
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
[
|
||||
{"type":"Name","value":"DriveInfo"},
|
||||
{"type":"NameAttribute","value":"[]"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Name","value":"drives"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Punctuation","value":"="},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"NameClass","value":"DriveInfo"},
|
||||
{"type":"Punctuation","value":"."},
|
||||
{"type":"Name","value":"GetDrives"},
|
||||
{"type":"Punctuation","value":"();"},
|
||||
{"type":"Text","value":"\n"},
|
||||
{"type":"Keyword","value":"foreach"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Punctuation","value":"("},
|
||||
{"type":"Name","value":"DriveInfo"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Name","value":"drive"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Keyword","value":"in"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Name","value":"drives"},
|
||||
{"type":"Punctuation","value":")"},
|
||||
{"type":"Text","value":"\n"},
|
||||
{"type":"Punctuation","value":"{"},
|
||||
{"type":"Text","value":"\n "},
|
||||
{"type":"Name","value":"IEnumerable"},
|
||||
{"type":"Punctuation","value":"\u003c"},
|
||||
{"type":"KeywordType","value":"string"},
|
||||
{"type":"Punctuation","value":"\u003e"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Name","value":"driveFolders"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Punctuation","value":"="},
|
||||
{"type":"Text","value":"\n "},
|
||||
{"type":"NameClass","value":"Directory"},
|
||||
{"type":"Punctuation","value":"."},
|
||||
{"type":"Name","value":"EnumerateDirectories"},
|
||||
{"type":"Punctuation","value":"("},
|
||||
{"type":"NameClass","value":"drive"},
|
||||
{"type":"Punctuation","value":"."},
|
||||
{"type":"NameClass","value":"RootDirectory"},
|
||||
{"type":"Punctuation","value":"."},
|
||||
{"type":"Name","value":"ToString"},
|
||||
{"type":"Punctuation","value":"());"},
|
||||
{"type":"Text","value":"\n\n "},
|
||||
{"type":"Keyword","value":"foreach"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Punctuation","value":"("},
|
||||
{"type":"KeywordType","value":"string"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Name","value":"dir"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Keyword","value":"in"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Name","value":"driveFolders"},
|
||||
{"type":"Punctuation","value":")"},
|
||||
{"type":"Text","value":"\n "},
|
||||
{"type":"Punctuation","value":"{"},
|
||||
{"type":"Text","value":"\n "},
|
||||
{"type":"NameClass","value":"Console"},
|
||||
{"type":"Punctuation","value":"."},
|
||||
{"type":"Name","value":"WriteLine"},
|
||||
{"type":"Punctuation","value":"("},
|
||||
{"type":"Name","value":"dir"},
|
||||
{"type":"Punctuation","value":");"},
|
||||
{"type":"Text","value":"\n "},
|
||||
{"type":"Punctuation","value":"}"},
|
||||
{"type":"Text","value":"\n"},
|
||||
{"type":"Punctuation","value":"}"},
|
||||
{"type":"Text","value":"\n"}
|
||||
]
|
|
@ -1,3 +0,0 @@
|
|||
:root {
|
||||
--variable-name: #fff;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
[
|
||||
{"type":"Punctuation","value":":"},
|
||||
{"type":"NameDecorator","value":"root"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"Punctuation","value":"{"},
|
||||
{"type":"Text","value":"\n "},
|
||||
{"type":"NameVariable","value":"--variable-name"},
|
||||
{"type":"Text","value":""},
|
||||
{"type":"Punctuation","value":":"},
|
||||
{"type":"Text","value":" "},
|
||||
{"type":"LiteralNumberHex","value":"#fff"},
|
||||
{"type":"Punctuation","value":";"},
|
||||
{"type":"Text","value":"\n"},
|
||||
{"type":"Punctuation","value":"}"},
|
||||
{"type":"Text","value":"\n"}
|
||||
]
|
|
@ -1,57 +0,0 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
func TestInclude(t *testing.T) {
|
||||
include := Include("other")
|
||||
actual := CompiledRules{
|
||||
"root": {{Rule: include}},
|
||||
"other": {
|
||||
{Rule: Rule{Pattern: "//.+", Type: Comment}},
|
||||
{Rule: Rule{Pattern: `"[^"]*"`, Type: String}},
|
||||
},
|
||||
}
|
||||
lexer := &RegexLexer{rules: actual}
|
||||
err := include.Mutator.(LexerMutator).MutateLexer(lexer.rules, "root", 0)
|
||||
assert.NoError(t, err)
|
||||
expected := CompiledRules{
|
||||
"root": {
|
||||
{Rule: Rule{
|
||||
Pattern: "//.+",
|
||||
Type: Comment,
|
||||
}},
|
||||
{Rule: Rule{
|
||||
Pattern: `"[^"]*"`,
|
||||
Type: String,
|
||||
}},
|
||||
},
|
||||
"other": {
|
||||
{Rule: Rule{
|
||||
Pattern: "//.+",
|
||||
Type: Comment,
|
||||
}},
|
||||
{Rule: Rule{
|
||||
Pattern: `"[^"]*"`,
|
||||
Type: String,
|
||||
}},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestCombine(t *testing.T) {
|
||||
l := MustNewLexer(nil, Rules{
|
||||
"root": {{`hello`, String, Combined("world", "bye", "space")}},
|
||||
"world": {{`world`, Name, nil}},
|
||||
"bye": {{`bye`, Name, nil}},
|
||||
"space": {{`\s+`, Whitespace, nil}},
|
||||
})
|
||||
it, err := l.Tokenise(nil, "hello world")
|
||||
assert.NoError(t, err)
|
||||
expected := []*Token{{String, `hello`}, {Whitespace, ` `}, {Name, `world`}}
|
||||
assert.Equal(t, expected, it.Tokens())
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package quick_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/chroma/quick"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
code := `package main
|
||||
|
||||
func main() { }
|
||||
`
|
||||
err := quick.Highlight(os.Stdout, code, "go", "html", "monokai")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Package quick provides simple, no-configuration source code highlighting.
|
||||
package quick
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
)
|
||||
|
||||
// Highlight some text.
|
||||
//
|
||||
// Lexer, formatter and style may be empty, in which case a best-effort is made.
|
||||
func Highlight(w io.Writer, source, lexer, formatter, style string) error {
|
||||
// Determine lexer.
|
||||
l := lexers.Get(lexer)
|
||||
if l == nil {
|
||||
l = lexers.Analyse(source)
|
||||
}
|
||||
if l == nil {
|
||||
l = lexers.Fallback
|
||||
}
|
||||
l = chroma.Coalesce(l)
|
||||
|
||||
// Determine formatter.
|
||||
f := formatters.Get(formatter)
|
||||
if f == nil {
|
||||
f = formatters.Fallback
|
||||
}
|
||||
|
||||
// Determine style.
|
||||
s := styles.Get(style)
|
||||
if s == nil {
|
||||
s = styles.Fallback
|
||||
}
|
||||
|
||||
it, err := l.Tokenise(nil, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Format(w, s, it)
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
func TestNewlineAtEndOfFile(t *testing.T) {
|
||||
l := Coalesce(MustNewLexer(&Config{EnsureNL: true}, Rules{
|
||||
"root": {
|
||||
{`(\w+)(\n)`, ByGroups(Keyword, Whitespace), nil},
|
||||
},
|
||||
}))
|
||||
it, err := l.Tokenise(nil, `hello`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*Token{{Keyword, "hello"}, {Whitespace, "\n"}}, it.Tokens())
|
||||
|
||||
l = Coalesce(MustNewLexer(nil, Rules{
|
||||
"root": {
|
||||
{`(\w+)(\n)`, ByGroups(Keyword, Whitespace), nil},
|
||||
},
|
||||
}))
|
||||
it, err = l.Tokenise(nil, `hello`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*Token{{Error, "hello"}}, it.Tokens())
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
func TestRemappingLexer(t *testing.T) {
|
||||
var lexer Lexer = MustNewLexer(nil, Rules{
|
||||
"root": {
|
||||
{`\s+`, Whitespace, nil},
|
||||
{`\w+`, Name, nil},
|
||||
},
|
||||
})
|
||||
lexer = TypeRemappingLexer(lexer, TypeMapping{
|
||||
{Name, Keyword, []string{"if", "else"}},
|
||||
})
|
||||
|
||||
it, err := lexer.Tokenise(nil, `if true then print else end`)
|
||||
assert.NoError(t, err)
|
||||
expected := []*Token{
|
||||
{Keyword, "if"}, {TextWhitespace, " "}, {Name, "true"}, {TextWhitespace, " "}, {Name, "then"},
|
||||
{TextWhitespace, " "}, {Name, "print"}, {TextWhitespace, " "}, {Keyword, "else"},
|
||||
{TextWhitespace, " "}, {Name, "end"},
|
||||
}
|
||||
actual := it.Tokens()
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
)
|
||||
|
||||
func TestStyleInherit(t *testing.T) {
|
||||
s, err := NewStyle("test", StyleEntries{
|
||||
Name: "bold #f00",
|
||||
NameVariable: "#fff",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, StyleEntry{Colour: 0x1000000, Bold: Yes}, s.Get(NameVariable))
|
||||
}
|
||||
|
||||
func TestStyleColours(t *testing.T) {
|
||||
s, err := NewStyle("test", StyleEntries{
|
||||
Name: "#f00 bg:#001 border:#ansiblue",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, StyleEntry{Colour: 0xff0001, Background: 0x000012, Border: 0x000100}, s.Get(Name))
|
||||
}
|
||||
|
||||
func TestStyleClone(t *testing.T) {
|
||||
parent, err := NewStyle("test", StyleEntries{
|
||||
Background: "bg:#ffffff",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
clone, err := parent.Builder().Add(Comment, "#0f0").Build()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "bg:#ffffff", clone.Get(Background).String())
|
||||
assert.Equal(t, "#00ff00 bg:#ffffff", clone.Get(Comment).String())
|
||||
assert.Equal(t, "bg:#ffffff", parent.Get(Comment).String())
|
||||
}
|
|
@ -1,351 +0,0 @@
|
|||
package fnmatch_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/danwakefield/fnmatch"
|
||||
)
|
||||
|
||||
// This is a set of tests ported from a set of tests for C fnmatch
|
||||
// found at http://www.mail-archive.com/bug-gnulib@gnu.org/msg14048.html
|
||||
func TestMatch(t *testing.T) {
|
||||
assert := func(p, s string) {
|
||||
if !fnmatch.Match(p, s, 0) {
|
||||
t.Errorf("Assertion failed: Match(%#v, %#v, 0)", p, s)
|
||||
}
|
||||
}
|
||||
assert("", "")
|
||||
assert("*", "")
|
||||
assert("*", "foo")
|
||||
assert("*", "bar")
|
||||
assert("*", "*")
|
||||
assert("**", "f")
|
||||
assert("**", "foo.txt")
|
||||
assert("*.*", "foo.txt")
|
||||
assert("foo*.txt", "foobar.txt")
|
||||
assert("foo.txt", "foo.txt")
|
||||
assert("foo\\.txt", "foo.txt")
|
||||
if fnmatch.Match("foo\\.txt", "foo.txt", fnmatch.FNM_NOESCAPE) {
|
||||
t.Errorf("Assertion failed: Match(%#v, %#v, FNM_NOESCAPE) == false", "foo\\.txt", "foo.txt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcard(t *testing.T) {
|
||||
// A wildcard pattern "*" should match anything
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
{"*", "", 0, true},
|
||||
{"*", "foo", 0, true},
|
||||
{"*", "*", 0, true},
|
||||
{"*", " ", 0, true},
|
||||
{"*", ".foo", 0, true},
|
||||
{"*", "わたし", 0, true},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardSlash(t *testing.T) {
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
// Should match / when flags are 0
|
||||
{"*", "foo/bar", 0, true},
|
||||
{"*", "/", 0, true},
|
||||
{"*", "/foo", 0, true},
|
||||
{"*", "foo/", 0, true},
|
||||
// Shouldnt match / when flags include FNM_PATHNAME
|
||||
{"*", "foo/bar", fnmatch.FNM_PATHNAME, false},
|
||||
{"*", "/", fnmatch.FNM_PATHNAME, false},
|
||||
{"*", "/foo", fnmatch.FNM_PATHNAME, false},
|
||||
{"*", "foo/", fnmatch.FNM_PATHNAME, false},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
for _, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardFNMPeriod(t *testing.T) {
|
||||
// FNM_PERIOD means that . is not matched in some circumstances.
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
{"*", ".foo", fnmatch.FNM_PERIOD, false},
|
||||
{"/*", "/.foo", fnmatch.FNM_PERIOD, true},
|
||||
{"/*", "/.foo", fnmatch.FNM_PERIOD | fnmatch.FNM_PATHNAME, false},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestionMark(t *testing.T) {
|
||||
//A question mark pattern "?" should match a single character
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
{"?", "", 0, false},
|
||||
{"?", "f", 0, true},
|
||||
{"?", ".", 0, true},
|
||||
{"?", "?", 0, true},
|
||||
{"?", "foo", 0, false},
|
||||
{"?", "わ", 0, true},
|
||||
{"?", "わた", 0, false},
|
||||
// Even '/' when flags are 0
|
||||
{"?", "/", 0, true},
|
||||
// Except '/' when flags include FNM_PATHNAME
|
||||
{"?", "/", fnmatch.FNM_PATHNAME, false},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestionMarkExceptions(t *testing.T) {
|
||||
//When flags include FNM_PERIOD a '?' might not match a '.' character.
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
{"?", ".", fnmatch.FNM_PERIOD, false},
|
||||
{"foo?", "foo.", fnmatch.FNM_PERIOD, true},
|
||||
{"/?", "/.", fnmatch.FNM_PERIOD, true},
|
||||
{"/?", "/.", fnmatch.FNM_PERIOD | fnmatch.FNM_PATHNAME, false},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRange(t *testing.T) {
|
||||
azPat := "[a-z]"
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
// Should match a single character inside its range
|
||||
{azPat, "a", 0, true},
|
||||
{azPat, "q", 0, true},
|
||||
{azPat, "z", 0, true},
|
||||
{"[わ]", "わ", 0, true},
|
||||
|
||||
// Should not match characters outside its range
|
||||
{azPat, "-", 0, false},
|
||||
{azPat, " ", 0, false},
|
||||
{azPat, "D", 0, false},
|
||||
{azPat, "é", 0, false},
|
||||
|
||||
//Should only match one character
|
||||
{azPat, "ab", 0, false},
|
||||
{azPat, "", 0, false},
|
||||
|
||||
// Should not consume more of the pattern than necessary
|
||||
{azPat + "foo", "afoo", 0, true},
|
||||
|
||||
// Should match '-' if it is the first/last character or is
|
||||
// backslash escaped
|
||||
{"[-az]", "-", 0, true},
|
||||
{"[-az]", "a", 0, true},
|
||||
{"[-az]", "b", 0, false},
|
||||
{"[az-]", "-", 0, true},
|
||||
{"[a\\-z]", "-", 0, true},
|
||||
{"[a\\-z]", "b", 0, false},
|
||||
|
||||
// ignore '\\' when FNM_NOESCAPE is given
|
||||
{"[a\\-z]", "\\", fnmatch.FNM_NOESCAPE, true},
|
||||
{"[a\\-z]", "-", fnmatch.FNM_NOESCAPE, false},
|
||||
|
||||
// Should be negated if starting with ^ or !"
|
||||
{"[^a-z]", "a", 0, false},
|
||||
{"[!a-z]", "b", 0, false},
|
||||
{"[!a-z]", "é", 0, true},
|
||||
{"[!a-z]", "わ", 0, true},
|
||||
|
||||
// Still match '-' if following the negation character
|
||||
{"[^-az]", "-", 0, false},
|
||||
{"[^-az]", "b", 0, true},
|
||||
|
||||
// Should support multiple characters/ranges
|
||||
{"[abc]", "a", 0, true},
|
||||
{"[abc]", "c", 0, true},
|
||||
{"[abc]", "d", 0, false},
|
||||
{"[a-cg-z]", "c", 0, true},
|
||||
{"[a-cg-z]", "h", 0, true},
|
||||
{"[a-cg-z]", "d", 0, false},
|
||||
|
||||
//Should not match '/' when flags is FNM_PATHNAME
|
||||
{"[abc/def]", "/", 0, true},
|
||||
{"[abc/def]", "/", fnmatch.FNM_PATHNAME, false},
|
||||
{"[.-0]", "/", 0, true}, // The range [.-0] includes /
|
||||
{"[.-0]", "/", fnmatch.FNM_PATHNAME, false},
|
||||
|
||||
// Should normally be case-sensitive
|
||||
{"[a-z]", "A", 0, false},
|
||||
{"[A-Z]", "a", 0, false},
|
||||
//Except when FNM_CASEFOLD is given
|
||||
{"[a-z]", "A", fnmatch.FNM_CASEFOLD, true},
|
||||
{"[A-Z]", "a", fnmatch.FNM_CASEFOLD, true},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackSlash(t *testing.T) {
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
//A backslash should escape the following characters
|
||||
{"\\\\", "\\", 0, true},
|
||||
{"\\*", "*", 0, true},
|
||||
{"\\*", "foo", 0, false},
|
||||
{"\\?", "?", 0, true},
|
||||
{"\\?", "f", 0, false},
|
||||
{"\\[a-z]", "[a-z]", 0, true},
|
||||
{"\\[a-z]", "a", 0, false},
|
||||
{"\\foo", "foo", 0, true},
|
||||
{"\\わ", "わ", 0, true},
|
||||
|
||||
// Unless FNM_NOESCAPE is given
|
||||
{"\\\\", "\\", fnmatch.FNM_NOESCAPE, false},
|
||||
{"\\\\", "\\\\", fnmatch.FNM_NOESCAPE, true},
|
||||
{"\\*", "foo", fnmatch.FNM_NOESCAPE, false},
|
||||
{"\\*", "\\*", fnmatch.FNM_NOESCAPE, true},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiteral(t *testing.T) {
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
//Literal characters should match themselves
|
||||
{"foo", "foo", 0, true},
|
||||
{"foo", "foobar", 0, false},
|
||||
{"foobar", "foo", 0, false},
|
||||
{"foo", "Foo", 0, false},
|
||||
{"わたし", "わたし", 0, true},
|
||||
// And perform case-folding when FNM_CASEFOLD is given
|
||||
{"foo", "FOO", fnmatch.FNM_CASEFOLD, true},
|
||||
{"FoO", "fOo", fnmatch.FNM_CASEFOLD, true},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFNMLeadingDir(t *testing.T) {
|
||||
cases := []struct {
|
||||
pattern string
|
||||
input string
|
||||
flags int
|
||||
want bool
|
||||
}{
|
||||
// FNM_LEADING_DIR should ignore trailing '/*'
|
||||
{"foo", "foo/bar", 0, false},
|
||||
{"foo", "foo/bar", fnmatch.FNM_LEADING_DIR, true},
|
||||
{"*", "foo/bar", fnmatch.FNM_PATHNAME, false},
|
||||
{"*", "foo/bar", fnmatch.FNM_PATHNAME | fnmatch.FNM_LEADING_DIR, true},
|
||||
}
|
||||
|
||||
for tc, c := range cases {
|
||||
got := fnmatch.Match(c.pattern, c.input, c.flags)
|
||||
if got != c.want {
|
||||
t.Errorf(
|
||||
"Testcase #%d failed: fnmatch.Match('%s', '%s', %d) should be %v not %v",
|
||||
tc, c.pattern, c.input, c.flags, c.want, got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,43 +0,0 @@
|
|||
package regexp2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIgnoreCase_Simple(t *testing.T) {
|
||||
r := MustCompile("aaamatch thisbbb", IgnoreCase)
|
||||
m, err := r.FindStringMatch("AaAMatch thisBBb")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("no match when one was expected")
|
||||
}
|
||||
if want, got := "AaAMatch thisBBb", m.String(); want != got {
|
||||
t.Fatalf("group 0 wanted '%v', got '%v'", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreCase_Inline(t *testing.T) {
|
||||
r := MustCompile("aaa(?i:match this)bbb", 0)
|
||||
m, err := r.FindStringMatch("aaaMaTcH ThIsbbb")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("no match when one was expected")
|
||||
}
|
||||
if want, got := "aaaMaTcH ThIsbbb", m.String(); want != got {
|
||||
t.Fatalf("group 0 wanted '%v', got '%v'", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreCase_Revert(t *testing.T) {
|
||||
|
||||
r := MustCompile("aaa(?-i:match this)bbb", IgnoreCase)
|
||||
m, err := r.FindStringMatch("AaAMatch thisBBb")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if m != nil {
|
||||
t.Fatalf("had a match but expected no match")
|
||||
}
|
||||
}
|
|
@ -1,409 +0,0 @@
|
|||
package regexp2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Process the file "testoutput1" from PCRE2 v10.21 (public domain)
|
||||
var totalCount, failCount = 0, 0
|
||||
|
||||
func TestPcre_Basics(t *testing.T) {
|
||||
defer func() {
|
||||
if failCount > 0 {
|
||||
t.Logf("%v of %v patterns failed", failCount, totalCount)
|
||||
}
|
||||
}()
|
||||
// open our test patterns file and run through it
|
||||
// validating results as we go
|
||||
file, err := os.Open("testoutput1")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// the high level structure of the file:
|
||||
// #comments - ignore only outside of the pattern
|
||||
// pattern (could be multi-line, could be surrounded by "" or //) after the / there are the options some we understand, some we don't
|
||||
// test case
|
||||
// 0: success case
|
||||
// \= Expect no match (ignored)
|
||||
// another test case
|
||||
// No Match
|
||||
//
|
||||
// another pattern ...etc
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
// main pattern loop
|
||||
for scanner.Scan() {
|
||||
// reading the file a line at a time
|
||||
line := scanner.Text()
|
||||
|
||||
if trim := strings.TrimSpace(line); trim == "" || strings.HasPrefix(trim, "#") {
|
||||
// skip blanks and comments
|
||||
continue
|
||||
}
|
||||
|
||||
patternStart := line[0]
|
||||
if patternStart != '/' && patternStart != '"' {
|
||||
// an error! expected a pattern but we didn't understand what was in the file
|
||||
t.Fatalf("Unknown file format, expected line to start with '/' or '\"', line in: %v", line)
|
||||
}
|
||||
|
||||
// start building our pattern, handling multi-line patterns
|
||||
pattern := line
|
||||
totalCount++
|
||||
|
||||
// keep appending the lines to our pattern string until we
|
||||
// find our closing tag, don't allow the first char to match on the
|
||||
// line start, but subsequent lines could end on the first char
|
||||
allowFirst := false
|
||||
for !containsEnder(line, patternStart, allowFirst) {
|
||||
if !scanner.Scan() {
|
||||
// an error! expected more pattern, but got eof
|
||||
t.Fatalf("Unknown file format, expected more pattern text, but got EOF, pattern so far: %v", pattern)
|
||||
}
|
||||
line = scanner.Text()
|
||||
pattern += fmt.Sprintf("\n%s", line)
|
||||
allowFirst = true
|
||||
}
|
||||
|
||||
// we have our raw pattern! -- we need to convert this to a compiled regex
|
||||
re := compileRawPattern(t, pattern)
|
||||
|
||||
var (
|
||||
capsIdx map[int]int
|
||||
m *Match
|
||||
toMatch string
|
||||
)
|
||||
// now we need to parse the test cases if there are any
|
||||
// they start with 4 spaces -- if we don't get a 4-space start then
|
||||
// we're back out to our next pattern
|
||||
for scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
|
||||
// blank line is our separator for a new pattern
|
||||
if strings.TrimSpace(line) == "" {
|
||||
break
|
||||
}
|
||||
|
||||
// could be either " " or "\= Expect"
|
||||
if strings.HasPrefix(line, "\\= Expect") {
|
||||
continue
|
||||
} else if strings.HasPrefix(line, " ") {
|
||||
// trim off leading spaces for our text to match
|
||||
toMatch = line[4:]
|
||||
// trim off trailing spaces too
|
||||
toMatch = strings.TrimRight(toMatch, " ")
|
||||
|
||||
m = matchString(t, re, toMatch)
|
||||
|
||||
capsIdx = make(map[int]int)
|
||||
continue
|
||||
//t.Fatalf("Expected match text to start with 4 spaces, instead got: '%v'", line)
|
||||
} else if strings.HasPrefix(line, "No match") {
|
||||
validateNoMatch(t, re, m)
|
||||
// no match means we're done
|
||||
continue
|
||||
} else if subs := matchGroup.FindStringSubmatch(line); len(subs) == 3 {
|
||||
gIdx, _ := strconv.Atoi(subs[1])
|
||||
if _, ok := capsIdx[gIdx]; !ok {
|
||||
capsIdx[gIdx] = 0
|
||||
}
|
||||
validateMatch(t, re, m, toMatch, subs[2], gIdx, capsIdx[gIdx])
|
||||
capsIdx[gIdx]++
|
||||
continue
|
||||
} else {
|
||||
// no match -- problem
|
||||
t.Fatalf("Unknown file format, expected match or match group but got '%v'", line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var matchGroup = regexp.MustCompile(`^\s*(\d+): (.*)`)
|
||||
|
||||
func problem(t *testing.T, input string, args ...interface{}) {
|
||||
failCount++
|
||||
t.Errorf(input, args...)
|
||||
}
|
||||
|
||||
func validateNoMatch(t *testing.T, re *Regexp, m *Match) {
|
||||
if re == nil || m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
problem(t, "Expected no match for pattern '%v', but got '%v'", re.pattern, m.String())
|
||||
}
|
||||
|
||||
func validateMatch(t *testing.T, re *Regexp, m *Match, toMatch, value string, idx, capIdx int) {
|
||||
if re == nil {
|
||||
// already error'd earlier up stream
|
||||
return
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
// we didn't match, but should have
|
||||
problem(t, "Expected match for pattern '%v' with input '%v', but got no match", re.pattern, toMatch)
|
||||
return
|
||||
}
|
||||
|
||||
g := m.Groups()
|
||||
if len(g) <= idx {
|
||||
problem(t, "Expected group %v does not exist in pattern '%v' with input '%v'", idx, re.pattern, toMatch)
|
||||
return
|
||||
}
|
||||
|
||||
if value == "<unset>" {
|
||||
// this means we shouldn't have a cap for this group
|
||||
if len(g[idx].Captures) > 0 {
|
||||
problem(t, "Expected no cap %v in group %v in pattern '%v' with input '%v'", g[idx].Captures[capIdx].String(), idx, re.pattern, toMatch)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(g[idx].Captures) <= capIdx {
|
||||
problem(t, "Expected cap %v does not exist in group %v in pattern '%v' with input '%v'", capIdx, idx, re.pattern, toMatch)
|
||||
return
|
||||
}
|
||||
|
||||
escp := unEscapeGroup(g[idx].String())
|
||||
//escp := unEscapeGroup(g[idx].Captures[capIdx].String())
|
||||
if escp != value {
|
||||
problem(t, "Expected '%v' but got '%v' for cap %v, group %v for pattern '%v' with input '%v'", value, escp, capIdx, idx, re.pattern, toMatch)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func compileRawPattern(t *testing.T, pattern string) *Regexp {
|
||||
// check our end for RegexOptions -trim them off
|
||||
index := strings.LastIndexAny(pattern, "/\"")
|
||||
//
|
||||
// Append "= Debug" to compare details between corefx and regexp2 on the PCRE test suite
|
||||
//
|
||||
var opts RegexOptions
|
||||
|
||||
if index+1 < len(pattern) {
|
||||
textOptions := pattern[index+1:]
|
||||
pattern = pattern[:index+1]
|
||||
// there are lots of complex options here
|
||||
for _, textOpt := range strings.Split(textOptions, ",") {
|
||||
switch textOpt {
|
||||
case "dupnames":
|
||||
// we don't know how to handle this...
|
||||
default:
|
||||
if strings.Contains(textOpt, "i") {
|
||||
opts |= IgnoreCase
|
||||
}
|
||||
if strings.Contains(textOpt, "s") {
|
||||
opts |= Singleline
|
||||
}
|
||||
if strings.Contains(textOpt, "m") {
|
||||
opts |= Multiline
|
||||
}
|
||||
if strings.Contains(textOpt, "x") {
|
||||
opts |= IgnorePatternWhitespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// trim off first and last char
|
||||
pattern = pattern[1 : len(pattern)-1]
|
||||
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
problem(t, "PANIC in compiling \"%v\": %v", pattern, rec)
|
||||
}
|
||||
}()
|
||||
re, err := Compile(pattern, opts)
|
||||
if err != nil {
|
||||
problem(t, "Error parsing \"%v\": %v", pattern, err)
|
||||
}
|
||||
return re
|
||||
}
|
||||
|
||||
func matchString(t *testing.T, re *Regexp, toMatch string) *Match {
|
||||
if re == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
re.MatchTimeout = time.Second * 1
|
||||
|
||||
escp := ""
|
||||
var err error
|
||||
if toMatch != "\\" {
|
||||
escp = unEscapeToMatch(toMatch)
|
||||
}
|
||||
m, err := re.FindStringMatch(escp)
|
||||
if err != nil {
|
||||
problem(t, "Error matching \"%v\" in pattern \"%v\": %v", toMatch, re.pattern, err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func containsEnder(line string, ender byte, allowFirst bool) bool {
|
||||
index := strings.LastIndexByte(line, ender)
|
||||
if index > 0 {
|
||||
return true
|
||||
} else if index == 0 && allowFirst {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unEscapeToMatch(line string) string {
|
||||
idx := strings.IndexRune(line, '\\')
|
||||
// no slashes means no unescape needed
|
||||
if idx == -1 {
|
||||
return line
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(line[:idx])
|
||||
// get the runes for the rest of the string -- we're going full parser scan on this
|
||||
|
||||
inEscape := false
|
||||
// take any \'s and convert them
|
||||
for i := idx; i < len(line); i++ {
|
||||
ch := line[i]
|
||||
if ch == '\\' {
|
||||
if inEscape {
|
||||
buf.WriteByte(ch)
|
||||
}
|
||||
inEscape = !inEscape
|
||||
continue
|
||||
}
|
||||
if inEscape {
|
||||
switch ch {
|
||||
case 'x':
|
||||
buf.WriteByte(scanHex(line, &i))
|
||||
case 'a':
|
||||
buf.WriteByte(0x07)
|
||||
case 'b':
|
||||
buf.WriteByte('\b')
|
||||
case 'e':
|
||||
buf.WriteByte(0x1b)
|
||||
case 'f':
|
||||
buf.WriteByte('\f')
|
||||
case 'n':
|
||||
buf.WriteByte('\n')
|
||||
case 'r':
|
||||
buf.WriteByte('\r')
|
||||
case 't':
|
||||
buf.WriteByte('\t')
|
||||
case 'v':
|
||||
buf.WriteByte(0x0b)
|
||||
default:
|
||||
if ch >= '0' && ch <= '7' {
|
||||
buf.WriteByte(scanOctal(line, &i))
|
||||
} else {
|
||||
buf.WriteByte(ch)
|
||||
//panic(fmt.Sprintf("unexpected escape '%v' in %v", string(ch), line))
|
||||
}
|
||||
}
|
||||
inEscape = false
|
||||
} else {
|
||||
buf.WriteByte(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func unEscapeGroup(val string) string {
|
||||
// use hex for chars 0x00-0x1f, 0x7f-0xff
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for i := 0; i < len(val); i++ {
|
||||
ch := val[i]
|
||||
if ch <= 0x1f || ch >= 0x7f {
|
||||
//write it as a \x00
|
||||
fmt.Fprintf(buf, "\\x%.2x", ch)
|
||||
} else {
|
||||
// write as-is
|
||||
buf.WriteByte(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func scanHex(line string, idx *int) byte {
|
||||
if *idx >= len(line)-2 {
|
||||
panic(fmt.Sprintf("not enough hex chars in %v at %v", line, *idx))
|
||||
}
|
||||
(*idx)++
|
||||
d1 := hexDigit(line[*idx])
|
||||
(*idx)++
|
||||
d2 := hexDigit(line[*idx])
|
||||
if d1 < 0 || d2 < 0 {
|
||||
panic("bad hex chars")
|
||||
}
|
||||
|
||||
return byte(d1*0x10 + d2)
|
||||
}
|
||||
|
||||
// Returns n <= 0xF for a hex digit.
|
||||
func hexDigit(ch byte) int {
|
||||
|
||||
if d := uint(ch - '0'); d <= 9 {
|
||||
return int(d)
|
||||
}
|
||||
|
||||
if d := uint(ch - 'a'); d <= 5 {
|
||||
return int(d + 0xa)
|
||||
}
|
||||
|
||||
if d := uint(ch - 'A'); d <= 5 {
|
||||
return int(d + 0xa)
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// Scans up to three octal digits (stops before exceeding 0377).
|
||||
func scanOctal(line string, idx *int) byte {
|
||||
// Consume octal chars only up to 3 digits and value 0377
|
||||
|
||||
// octals can be 3,2, or 1 digit
|
||||
c := 3
|
||||
|
||||
if diff := len(line) - *idx; c > diff {
|
||||
c = diff
|
||||
}
|
||||
|
||||
i := 0
|
||||
d := int(line[*idx] - '0')
|
||||
for c > 0 && d <= 7 {
|
||||
i *= 8
|
||||
i += d
|
||||
|
||||
c--
|
||||
(*idx)++
|
||||
if *idx < len(line) {
|
||||
d = int(line[*idx] - '0')
|
||||
}
|
||||
}
|
||||
(*idx)--
|
||||
|
||||
// Octal codes only go up to 255. Any larger and the behavior that Perl follows
|
||||
// is simply to truncate the high bits.
|
||||
i &= 0xFF
|
||||
|
||||
return byte(i)
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
package regexp2
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkLiteral(b *testing.B) {
|
||||
x := strings.Repeat("x", 50) + "y"
|
||||
b.StopTimer()
|
||||
re := MustCompile("y", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotLiteral(b *testing.B) {
|
||||
x := strings.Repeat("x", 50) + "y"
|
||||
b.StopTimer()
|
||||
re := MustCompile(".y", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchClass(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := strings.Repeat("xxxx", 20) + "w"
|
||||
re := MustCompile("[abcdw]", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchClass_InRange(b *testing.B) {
|
||||
b.StopTimer()
|
||||
// 'b' is between 'a' and 'c', so the charclass
|
||||
// range checking is no help here.
|
||||
x := strings.Repeat("bbbb", 20) + "c"
|
||||
re := MustCompile("[ac]", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func BenchmarkReplaceAll(b *testing.B) {
|
||||
x := "abcdefghijklmnopqrstuvwxyz"
|
||||
b.StopTimer()
|
||||
re := MustCompile("[cjrw]", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
re.ReplaceAllString(x, "")
|
||||
}
|
||||
}
|
||||
*/
|
||||
func BenchmarkAnchoredLiteralShortNonMatch(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcdefghijklmnopqrstuvwxyz"
|
||||
re := MustCompile("^zbc(d|e)", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); m || err != nil {
|
||||
b.Fatalf("unexpected match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAnchoredLiteralLongNonMatch(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
data := "abcdefghijklmnopqrstuvwxyz"
|
||||
x := make([]rune, 32768*len(data))
|
||||
for i := 0; i < 32768; /*(2^15)*/ i++ {
|
||||
for j := 0; j < len(data); j++ {
|
||||
x[i*len(data)+j] = rune(data[j])
|
||||
}
|
||||
}
|
||||
|
||||
re := MustCompile("^zbc(d|e)", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchRunes(x); m || err != nil {
|
||||
b.Fatalf("unexpected match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAnchoredShortMatch(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcdefghijklmnopqrstuvwxyz"
|
||||
re := MustCompile("^.bc(d|e)", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAnchoredLongMatch(b *testing.B) {
|
||||
b.StopTimer()
|
||||
data := "abcdefghijklmnopqrstuvwxyz"
|
||||
x := make([]rune, 32768*len(data))
|
||||
for i := 0; i < 32768; /*(2^15)*/ i++ {
|
||||
for j := 0; j < len(data); j++ {
|
||||
x[i*len(data)+j] = rune(data[j])
|
||||
}
|
||||
}
|
||||
|
||||
re := MustCompile("^.bc(d|e)", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchRunes(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOnePassShortA(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcddddddeeeededd"
|
||||
re := MustCompile("^.bc(d|e)*$", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotOnePassShortA(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcddddddeeeededd"
|
||||
re := MustCompile(".bc(d|e)*$", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOnePassShortB(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcddddddeeeededd"
|
||||
re := MustCompile("^.bc(?:d|e)*$", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotOnePassShortB(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcddddddeeeededd"
|
||||
re := MustCompile(".bc(?:d|e)*$", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOnePassLongPrefix(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcdefghijklmnopqrstuvwxyz"
|
||||
re := MustCompile("^abcdefghijklmnopqrstuvwxyz.*$", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOnePassLongNotPrefix(b *testing.B) {
|
||||
b.StopTimer()
|
||||
x := "abcdefghijklmnopqrstuvwxyz"
|
||||
re := MustCompile("^.bcdefghijklmnopqrstuvwxyz.*$", 0)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(x); !m || err != nil {
|
||||
b.Fatalf("no match or error! %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var text []rune
|
||||
|
||||
func makeText(n int) []rune {
|
||||
if len(text) >= n {
|
||||
return text[:n]
|
||||
}
|
||||
text = make([]rune, n)
|
||||
x := ^uint32(0)
|
||||
for i := range text {
|
||||
x += x
|
||||
x ^= 1
|
||||
if int32(x) < 0 {
|
||||
x ^= 0x88888eef
|
||||
}
|
||||
if x%31 == 0 {
|
||||
text[i] = '\n'
|
||||
} else {
|
||||
text[i] = rune(x%(0x7E+1-0x20) + 0x20)
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func benchmark(b *testing.B, re string, n int) {
|
||||
r := MustCompile(re, 0)
|
||||
t := makeText(n)
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(n))
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := r.MatchRunes(t); m {
|
||||
b.Fatal("match!")
|
||||
} else if err != nil {
|
||||
b.Fatalf("Err %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
easy0 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ$"
|
||||
easy1 = "A[AB]B[BC]C[CD]D[DE]E[EF]F[FG]G[GH]H[HI]I[IJ]J$"
|
||||
medium = "[XYZ]ABCDEFGHIJKLMNOPQRSTUVWXYZ$"
|
||||
hard = "[ -~]*ABCDEFGHIJKLMNOPQRSTUVWXYZ$"
|
||||
hard1 = "ABCD|CDEF|EFGH|GHIJ|IJKL|KLMN|MNOP|OPQR|QRST|STUV|UVWX|WXYZ"
|
||||
parens = "([ -~])*(A)(B)(C)(D)(E)(F)(G)(H)(I)(J)(K)(L)(M)" +
|
||||
"(N)(O)(P)(Q)(R)(S)(T)(U)(V)(W)(X)(Y)(Z)$"
|
||||
)
|
||||
|
||||
func BenchmarkMatchEasy0_32(b *testing.B) { benchmark(b, easy0, 32<<0) }
|
||||
func BenchmarkMatchEasy0_1K(b *testing.B) { benchmark(b, easy0, 1<<10) }
|
||||
func BenchmarkMatchEasy0_32K(b *testing.B) { benchmark(b, easy0, 32<<10) }
|
||||
func BenchmarkMatchEasy0_1M(b *testing.B) { benchmark(b, easy0, 1<<20) }
|
||||
func BenchmarkMatchEasy0_32M(b *testing.B) { benchmark(b, easy0, 32<<20) }
|
||||
func BenchmarkMatchEasy1_32(b *testing.B) { benchmark(b, easy1, 32<<0) }
|
||||
func BenchmarkMatchEasy1_1K(b *testing.B) { benchmark(b, easy1, 1<<10) }
|
||||
func BenchmarkMatchEasy1_32K(b *testing.B) { benchmark(b, easy1, 32<<10) }
|
||||
func BenchmarkMatchEasy1_1M(b *testing.B) { benchmark(b, easy1, 1<<20) }
|
||||
func BenchmarkMatchEasy1_32M(b *testing.B) { benchmark(b, easy1, 32<<20) }
|
||||
func BenchmarkMatchMedium_32(b *testing.B) { benchmark(b, medium, 32<<0) }
|
||||
func BenchmarkMatchMedium_1K(b *testing.B) { benchmark(b, medium, 1<<10) }
|
||||
func BenchmarkMatchMedium_32K(b *testing.B) { benchmark(b, medium, 32<<10) }
|
||||
func BenchmarkMatchMedium_1M(b *testing.B) { benchmark(b, medium, 1<<20) }
|
||||
func BenchmarkMatchMedium_32M(b *testing.B) { benchmark(b, medium, 32<<20) }
|
||||
func BenchmarkMatchHard_32(b *testing.B) { benchmark(b, hard, 32<<0) }
|
||||
func BenchmarkMatchHard_1K(b *testing.B) { benchmark(b, hard, 1<<10) }
|
||||
func BenchmarkMatchHard_32K(b *testing.B) { benchmark(b, hard, 32<<10) }
|
||||
func BenchmarkMatchHard_1M(b *testing.B) { benchmark(b, hard, 1<<20) }
|
||||
func BenchmarkMatchHard_32M(b *testing.B) { benchmark(b, hard, 32<<20) }
|
||||
func BenchmarkMatchHard1_32(b *testing.B) { benchmark(b, hard1, 32<<0) }
|
||||
func BenchmarkMatchHard1_1K(b *testing.B) { benchmark(b, hard1, 1<<10) }
|
||||
func BenchmarkMatchHard1_32K(b *testing.B) { benchmark(b, hard1, 32<<10) }
|
||||
func BenchmarkMatchHard1_1M(b *testing.B) { benchmark(b, hard1, 1<<20) }
|
||||
func BenchmarkMatchHard1_32M(b *testing.B) { benchmark(b, hard1, 32<<20) }
|
||||
|
||||
// TestProgramTooLongForBacktrack tests that a regex which is too long
|
||||
// for the backtracker still executes properly.
|
||||
func TestProgramTooLongForBacktrack(t *testing.T) {
|
||||
longRegex := MustCompile(`(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|twentyone|twentytwo|twentythree|twentyfour|twentyfive|twentysix|twentyseven|twentyeight|twentynine|thirty|thirtyone|thirtytwo|thirtythree|thirtyfour|thirtyfive|thirtysix|thirtyseven|thirtyeight|thirtynine|forty|fortyone|fortytwo|fortythree|fortyfour|fortyfive|fortysix|fortyseven|fortyeight|fortynine|fifty|fiftyone|fiftytwo|fiftythree|fiftyfour|fiftyfive|fiftysix|fiftyseven|fiftyeight|fiftynine|sixty|sixtyone|sixtytwo|sixtythree|sixtyfour|sixtyfive|sixtysix|sixtyseven|sixtyeight|sixtynine|seventy|seventyone|seventytwo|seventythree|seventyfour|seventyfive|seventysix|seventyseven|seventyeight|seventynine|eighty|eightyone|eightytwo|eightythree|eightyfour|eightyfive|eightysix|eightyseven|eightyeight|eightynine|ninety|ninetyone|ninetytwo|ninetythree|ninetyfour|ninetyfive|ninetysix|ninetyseven|ninetyeight|ninetynine|onehundred)`, 0)
|
||||
|
||||
if m, err := longRegex.MatchString("two"); !m {
|
||||
t.Errorf("longRegex.MatchString(\"two\") was false, want true")
|
||||
} else if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
if m, err := longRegex.MatchString("xxx"); m {
|
||||
t.Errorf("longRegex.MatchString(\"xxx\") was true, want false")
|
||||
} else if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLeading(b *testing.B) {
|
||||
b.StopTimer()
|
||||
r := MustCompile("[a-q][^u-z]{13}x", 0)
|
||||
inp := makeText(1000000)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := r.MatchRunes(inp); !m {
|
||||
b.Errorf("Expected match")
|
||||
} else if err != nil {
|
||||
b.Errorf("Error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,915 +0,0 @@
|
|||
package regexp2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dlclark/regexp2/syntax"
|
||||
)
|
||||
|
||||
func TestBacktrack_CatastrophicTimeout(t *testing.T) {
|
||||
r, err := Compile("(.+)*\\?", 0)
|
||||
r.MatchTimeout = time.Millisecond * 1
|
||||
t.Logf("code dump: %v", r.code.Dump())
|
||||
m, err := r.FindStringMatch("Do you think you found the problem string!")
|
||||
if err == nil {
|
||||
t.Errorf("expected timeout err")
|
||||
}
|
||||
if m != nil {
|
||||
t.Errorf("Expected no match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPrefix(t *testing.T) {
|
||||
r := MustCompile(`^\s*-TEST`, 0)
|
||||
if r.code.FcPrefix == nil {
|
||||
t.Fatalf("Expected prefix set [-\\s] but was nil")
|
||||
}
|
||||
if r.code.FcPrefix.PrefixSet.String() != "[-\\s]" {
|
||||
t.Fatalf("Expected prefix set [\\s-] but was %v", r.code.FcPrefix.PrefixSet.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetInCode(t *testing.T) {
|
||||
r := MustCompile(`(?<body>\s*(?<name>.+))`, 0)
|
||||
t.Logf("code dump: %v", r.code.Dump())
|
||||
if want, got := 1, len(r.code.Sets); want != got {
|
||||
t.Fatalf("r.code.Sets wanted %v, got %v", want, got)
|
||||
}
|
||||
if want, got := "[\\s]", r.code.Sets[0].String(); want != got {
|
||||
t.Fatalf("first set wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexp_Basic(t *testing.T) {
|
||||
r, err := Compile("test(?<named>ing)?", 0)
|
||||
//t.Logf("code dump: %v", r.code.Dump())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected compile err: %v", err)
|
||||
}
|
||||
m, err := r.FindStringMatch("this is a testing stuff")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected match err: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Error("Nil match, expected success")
|
||||
} else {
|
||||
//t.Logf("Match: %v", m.dump())
|
||||
}
|
||||
}
|
||||
|
||||
// check all our functions and properties around basic capture groups and referential for Group 0
|
||||
func TestCapture_Basic(t *testing.T) {
|
||||
r := MustCompile(`.*\B(SUCCESS)\B.*`, 0)
|
||||
m, err := r.FindStringMatch("adfadsfSUCCESSadsfadsf")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected match error: %v", err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
t.Fatalf("Should have matched")
|
||||
}
|
||||
if want, got := "adfadsfSUCCESSadsfadsf", m.String(); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := 0, m.Index; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := 22, m.Length; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := 1, len(m.Captures); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
|
||||
if want, got := m.String(), m.Captures[0].String(); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := 0, m.Captures[0].Index; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := 22, m.Captures[0].Length; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
|
||||
g := m.Groups()
|
||||
if want, got := 2, len(g); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
// group 0 is always the match
|
||||
if want, got := m.String(), g[0].String(); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := 1, len(g[0].Captures); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
// group 0's capture is always the match
|
||||
if want, got := m.Captures[0].String(), g[0].Captures[0].String(); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
|
||||
// group 1 is our first explicit group (unnamed)
|
||||
if want, got := 7, g[1].Index; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := 7, g[1].Length; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := "SUCCESS", g[1].String(); want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeUnescape_Basic(t *testing.T) {
|
||||
s1 := "#$^*+(){}<>\\|. "
|
||||
s2 := Escape(s1)
|
||||
s3, err := Unescape(s2)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error during unescape: %v", err)
|
||||
}
|
||||
|
||||
//confirm one way
|
||||
if want, got := `\#\$\^\*\+\(\)\{\}<>\\\|\.\ `, s2; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
|
||||
//confirm round-trip
|
||||
if want, got := s1, s3; want != got {
|
||||
t.Fatalf("Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGroups_Basic(t *testing.T) {
|
||||
type d struct {
|
||||
p string
|
||||
s string
|
||||
name []string
|
||||
num []int
|
||||
strs []string
|
||||
}
|
||||
data := []d{
|
||||
d{"(?<first_name>\\S+)\\s(?<last_name>\\S+)", // example
|
||||
"Ryan Byington",
|
||||
[]string{"0", "first_name", "last_name"},
|
||||
[]int{0, 1, 2},
|
||||
[]string{"Ryan Byington", "Ryan", "Byington"}},
|
||||
d{"((?<One>abc)\\d+)?(?<Two>xyz)(.*)", // example
|
||||
"abc208923xyzanqnakl",
|
||||
[]string{"0", "1", "2", "One", "Two"},
|
||||
[]int{0, 1, 2, 3, 4},
|
||||
[]string{"abc208923xyzanqnakl", "abc208923", "anqnakl", "abc", "xyz"}},
|
||||
d{"((?<256>abc)\\d+)?(?<16>xyz)(.*)", // numeric names
|
||||
"0272saasdabc8978xyz][]12_+-",
|
||||
[]string{"0", "1", "2", "16", "256"},
|
||||
[]int{0, 1, 2, 16, 256},
|
||||
[]string{"abc8978xyz][]12_+-", "abc8978", "][]12_+-", "xyz", "abc"}},
|
||||
d{"((?<4>abc)(?<digits>\\d+))?(?<2>xyz)(?<everything_else>.*)", // mix numeric and string names
|
||||
"0272saasdabc8978xyz][]12_+-",
|
||||
[]string{"0", "1", "2", "digits", "4", "everything_else"},
|
||||
[]int{0, 1, 2, 3, 4, 5},
|
||||
[]string{"abc8978xyz][]12_+-", "abc8978", "xyz", "8978", "abc", "][]12_+-"}},
|
||||
d{"(?<first_name>\\S+)\\s(?<first_name>\\S+)", // dupe string names
|
||||
"Ryan Byington",
|
||||
[]string{"0", "first_name"},
|
||||
[]int{0, 1},
|
||||
[]string{"Ryan Byington", "Byington"}},
|
||||
d{"(?<15>\\S+)\\s(?<15>\\S+)", // dupe numeric names
|
||||
"Ryan Byington",
|
||||
[]string{"0", "15"},
|
||||
[]int{0, 15},
|
||||
[]string{"Ryan Byington", "Byington"}},
|
||||
// *** repeated from above, but with alt cap syntax ***
|
||||
d{"(?'first_name'\\S+)\\s(?'last_name'\\S+)", //example
|
||||
"Ryan Byington",
|
||||
[]string{"0", "first_name", "last_name"},
|
||||
[]int{0, 1, 2},
|
||||
[]string{"Ryan Byington", "Ryan", "Byington"}},
|
||||
d{"((?'One'abc)\\d+)?(?'Two'xyz)(.*)", // example
|
||||
"abc208923xyzanqnakl",
|
||||
[]string{"0", "1", "2", "One", "Two"},
|
||||
[]int{0, 1, 2, 3, 4},
|
||||
[]string{"abc208923xyzanqnakl", "abc208923", "anqnakl", "abc", "xyz"}},
|
||||
d{"((?'256'abc)\\d+)?(?'16'xyz)(.*)", // numeric names
|
||||
"0272saasdabc8978xyz][]12_+-",
|
||||
[]string{"0", "1", "2", "16", "256"},
|
||||
[]int{0, 1, 2, 16, 256},
|
||||
[]string{"abc8978xyz][]12_+-", "abc8978", "][]12_+-", "xyz", "abc"}},
|
||||
d{"((?'4'abc)(?'digits'\\d+))?(?'2'xyz)(?'everything_else'.*)", // mix numeric and string names
|
||||
"0272saasdabc8978xyz][]12_+-",
|
||||
[]string{"0", "1", "2", "digits", "4", "everything_else"},
|
||||
[]int{0, 1, 2, 3, 4, 5},
|
||||
[]string{"abc8978xyz][]12_+-", "abc8978", "xyz", "8978", "abc", "][]12_+-"}},
|
||||
d{"(?'first_name'\\S+)\\s(?'first_name'\\S+)", // dupe string names
|
||||
"Ryan Byington",
|
||||
[]string{"0", "first_name"},
|
||||
[]int{0, 1},
|
||||
[]string{"Ryan Byington", "Byington"}},
|
||||
d{"(?'15'\\S+)\\s(?'15'\\S+)", // dupe numeric names
|
||||
"Ryan Byington",
|
||||
[]string{"0", "15"},
|
||||
[]int{0, 15},
|
||||
[]string{"Ryan Byington", "Byington"}},
|
||||
}
|
||||
|
||||
fatalf := func(re *Regexp, v d, format string, args ...interface{}) {
|
||||
args = append(args, v, re.code.Dump())
|
||||
|
||||
t.Fatalf(format+" using test data: %#v\ndump:%v", args...)
|
||||
}
|
||||
|
||||
validateGroupNamesNumbers := func(re *Regexp, v d) {
|
||||
if len(v.name) != len(v.num) {
|
||||
fatalf(re, v, "Invalid data, group name count and number count must match")
|
||||
}
|
||||
|
||||
groupNames := re.GetGroupNames()
|
||||
if !reflect.DeepEqual(groupNames, v.name) {
|
||||
fatalf(re, v, "group names expected: %v, actual: %v", v.name, groupNames)
|
||||
}
|
||||
groupNums := re.GetGroupNumbers()
|
||||
if !reflect.DeepEqual(groupNums, v.num) {
|
||||
fatalf(re, v, "group numbers expected: %v, actual: %v", v.num, groupNums)
|
||||
}
|
||||
// make sure we can freely get names and numbers from eachother
|
||||
for i := range groupNums {
|
||||
if want, got := groupNums[i], re.GroupNumberFromName(groupNames[i]); want != got {
|
||||
fatalf(re, v, "group num from name Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
if want, got := groupNames[i], re.GroupNameFromNumber(groupNums[i]); want != got {
|
||||
fatalf(re, v, "group name from num Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
// compile the regex
|
||||
re := MustCompile(v.p, 0)
|
||||
|
||||
// validate our group name/num info before execute
|
||||
validateGroupNamesNumbers(re, v)
|
||||
|
||||
m, err := re.FindStringMatch(v.s)
|
||||
if err != nil {
|
||||
fatalf(re, v, "Unexpected error in match: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
fatalf(re, v, "Match is nil")
|
||||
}
|
||||
if want, got := len(v.strs), m.GroupCount(); want != got {
|
||||
fatalf(re, v, "GroupCount() Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
g := m.Groups()
|
||||
if want, got := len(v.strs), len(g); want != got {
|
||||
fatalf(re, v, "len(m.Groups()) Wanted '%v'\nGot '%v'", want, got)
|
||||
}
|
||||
// validate each group's value from the execute
|
||||
for i := range v.name {
|
||||
grp1 := m.GroupByName(v.name[i])
|
||||
grp2 := m.GroupByNumber(v.num[i])
|
||||
// should be identical reference
|
||||
if grp1 != grp2 {
|
||||
fatalf(re, v, "Expected GroupByName and GroupByNumber to return same result for %v, %v", v.name[i], v.num[i])
|
||||
}
|
||||
if want, got := v.strs[i], grp1.String(); want != got {
|
||||
fatalf(re, v, "Value[%v] Wanted '%v'\nGot '%v'", i, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// validate our group name/num info after execute
|
||||
validateGroupNamesNumbers(re, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErr_GroupName(t *testing.T) {
|
||||
// group 0 is off limits
|
||||
if _, err := Compile("foo(?<0>bar)", 0); err == nil {
|
||||
t.Fatalf("zero group, expected error during compile")
|
||||
} else if want, got := "error parsing regexp: capture number cannot be zero in `foo(?<0>bar)`", err.Error(); want != got {
|
||||
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
|
||||
}
|
||||
if _, err := Compile("foo(?'0'bar)", 0); err == nil {
|
||||
t.Fatalf("zero group, expected error during compile")
|
||||
} else if want, got := "error parsing regexp: capture number cannot be zero in `foo(?'0'bar)`", err.Error(); want != got {
|
||||
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
|
||||
}
|
||||
|
||||
// group tag can't start with a num
|
||||
if _, err := Compile("foo(?<1bar>)", 0); err == nil {
|
||||
t.Fatalf("invalid group name, expected error during compile")
|
||||
} else if want, got := "error parsing regexp: invalid group name: group names must begin with a word character and have a matching terminator in `foo(?<1bar>)`", err.Error(); want != got {
|
||||
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
|
||||
}
|
||||
if _, err := Compile("foo(?'1bar')", 0); err == nil {
|
||||
t.Fatalf("invalid group name, expected error during compile")
|
||||
} else if want, got := "error parsing regexp: invalid group name: group names must begin with a word character and have a matching terminator in `foo(?'1bar')`", err.Error(); want != got {
|
||||
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
|
||||
}
|
||||
|
||||
// missing closing group tag
|
||||
if _, err := Compile("foo(?<bar)", 0); err == nil {
|
||||
t.Fatalf("invalid group name, expected error during compile")
|
||||
} else if want, got := "error parsing regexp: invalid group name: group names must begin with a word character and have a matching terminator in `foo(?<bar)`", err.Error(); want != got {
|
||||
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
|
||||
}
|
||||
if _, err := Compile("foo(?'bar)", 0); err == nil {
|
||||
t.Fatalf("invalid group name, expected error during compile")
|
||||
} else if want, got := "error parsing regexp: invalid group name: group names must begin with a word character and have a matching terminator in `foo(?'bar)`", err.Error(); want != got {
|
||||
t.Fatalf("invalid error text, want '%v', got '%v'", want, got)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConstantUneffected(t *testing.T) {
|
||||
// had a bug where "constant" sets would get modified with alternations and be broken in memory until restart
|
||||
// this meant that if you used a known-set (like \s) in a larger set it would "poison" \s for the process
|
||||
re := MustCompile(`(\s|\*)test\s`, 0)
|
||||
if want, got := 2, len(re.code.Sets); want != got {
|
||||
t.Fatalf("wanted %v sets, got %v", want, got)
|
||||
}
|
||||
if want, got := "[\\*\\s]", re.code.Sets[0].String(); want != got {
|
||||
t.Fatalf("wanted set 0 %v, got %v", want, got)
|
||||
}
|
||||
if want, got := "[\\s]", re.code.Sets[1].String(); want != got {
|
||||
t.Fatalf("wanted set 1 %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternationConstAndEscape(t *testing.T) {
|
||||
re := MustCompile(`\:|\s`, 0)
|
||||
if want, got := 1, len(re.code.Sets); want != got {
|
||||
t.Fatalf("wanted %v sets, got %v", want, got)
|
||||
}
|
||||
if want, got := "[:\\s]", re.code.Sets[0].String(); want != got {
|
||||
t.Fatalf("wanted set 0 %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartingCharsOptionalNegate(t *testing.T) {
|
||||
// to maintain matching with the corefx we've made the negative char classes be negative and the
|
||||
// categories they contain positive. This means they're not combinable or suitable for prefixes.
|
||||
// In general this could be a fine thing since negatives are extremely wide groups and not
|
||||
// missing much on prefix optimizations.
|
||||
|
||||
// the below expression *could* have a prefix of [\S\d] but
|
||||
// this requires a change in charclass.go when setting
|
||||
// NotSpaceClass = getCharSetFromCategoryString()
|
||||
// to negate the individual categories rather than the CharSet itself
|
||||
// this would deviate from corefx
|
||||
|
||||
re := MustCompile(`(^(\S{2} )?\S{2}(\d+|/) *\S{3}\S{3} ?\d{2,4}[A-Z] ?\d{2}[A-Z]{3}|(\S{2} )?\d{2,4})`, 0)
|
||||
if re.code.FcPrefix != nil {
|
||||
t.Fatalf("FcPrefix wanted nil, got %v", re.code.FcPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNegativeDigit(t *testing.T) {
|
||||
re := MustCompile(`\D`, 0)
|
||||
if want, got := 1, len(re.code.Sets); want != got {
|
||||
t.Fatalf("wanted %v sets, got %v", want, got)
|
||||
}
|
||||
|
||||
if want, got := "[\\P{Nd}]", re.code.Sets[0].String(); want != got {
|
||||
t.Fatalf("wanted set 0 %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunNegativeDigit(t *testing.T) {
|
||||
re := MustCompile(`\D`, 0)
|
||||
m, err := re.MatchString("this is a test")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancellingClasses(t *testing.T) {
|
||||
// [\w\W\s] should become "." because it means "anything"
|
||||
re := MustCompile(`[\w\W\s]`, 0)
|
||||
if want, got := 1, len(re.code.Sets); want != got {
|
||||
t.Fatalf("wanted %v sets, got %v", want, got)
|
||||
}
|
||||
if want, got := syntax.AnyClass().String(), re.code.Sets[0].String(); want != got {
|
||||
t.Fatalf("wanted set 0 %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcatLoopCaptureSet(t *testing.T) {
|
||||
//(A|B)*?CD different Concat/Loop/Capture/Set (had [A-Z] should be [AB])
|
||||
// we were not copying the Sets in the prefix FC stack, so the underlying sets were unexpectedly mutating
|
||||
// so set [AB] becomes [ABC] when we see the the static C in FC stack generation (which are the valid start chars),
|
||||
// but that was mutating the tree node's original set [AB] because even though we copied the slie header,
|
||||
// the two header's pointed to the same underlying byte array...which was mutated.
|
||||
|
||||
re := MustCompile(`(A|B)*CD`, 0)
|
||||
if want, got := 1, len(re.code.Sets); want != got {
|
||||
t.Fatalf("wanted %v sets, got %v", want, got)
|
||||
}
|
||||
if want, got := "[AB]", re.code.Sets[0].String(); want != got {
|
||||
t.Fatalf("wanted set 0 %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstcharsIgnoreCase(t *testing.T) {
|
||||
//((?i)AB(?-i)C|D)E different Firstchars (had [da] should be [ad])
|
||||
// we were not canonicalizing when converting the prefix set to lower case
|
||||
// so our set's were potentially not searching properly
|
||||
re := MustCompile(`((?i)AB(?-i)C|D)E`, 0)
|
||||
|
||||
if re.code.FcPrefix == nil {
|
||||
t.Fatalf("wanted prefix, got nil")
|
||||
}
|
||||
|
||||
if want, got := "[ad]", re.code.FcPrefix.PrefixSet.String(); want != got {
|
||||
t.Fatalf("wanted prefix %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepeatingGroup(t *testing.T) {
|
||||
re := MustCompile(`(data?)+`, 0)
|
||||
|
||||
m, err := re.FindStringMatch("datadat")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
g := m.GroupByNumber(1)
|
||||
if g == nil {
|
||||
t.Fatalf("Expected group")
|
||||
}
|
||||
|
||||
if want, got := 2, len(g.Captures); want != got {
|
||||
t.Fatalf("wanted cap count %v, got %v", want, got)
|
||||
}
|
||||
|
||||
if want, got := g.Captures[1].String(), g.Capture.String(); want != got {
|
||||
t.Fatalf("expected last capture of the group to be embedded")
|
||||
}
|
||||
|
||||
if want, got := "data", g.Captures[0].String(); want != got {
|
||||
t.Fatalf("expected cap 0 to be %v, got %v", want, got)
|
||||
}
|
||||
if want, got := "dat", g.Captures[1].String(); want != got {
|
||||
t.Fatalf("expected cap 1 to be %v, got %v", want, got)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFindNextMatch_Basic(t *testing.T) {
|
||||
re := MustCompile(`(T|E)(?=h|E|S|$)`, 0)
|
||||
m, err := re.FindStringMatch(`This is a TEST`)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err 0: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("Expected match 0")
|
||||
}
|
||||
if want, got := 0, m.Index; want != got {
|
||||
t.Fatalf("expected match 0 to start at %v, got %v", want, got)
|
||||
}
|
||||
|
||||
m, err = re.FindNextMatch(m)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err 1: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("Expected match 1")
|
||||
}
|
||||
if want, got := 10, m.Index; want != got {
|
||||
t.Fatalf("expected match 1 to start at %v, got %v", want, got)
|
||||
}
|
||||
|
||||
m, err = re.FindNextMatch(m)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err 2: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("Expected match 2")
|
||||
}
|
||||
if want, got := 11, m.Index; want != got {
|
||||
t.Fatalf("expected match 2 to start at %v, got %v", want, got)
|
||||
}
|
||||
|
||||
m, err = re.FindNextMatch(m)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err 3: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("Expected match 3")
|
||||
}
|
||||
if want, got := 13, m.Index; want != got {
|
||||
t.Fatalf("expected match 3 to start at %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnicodeSupplementaryCharSetMatch(t *testing.T) {
|
||||
//0x2070E 0x20731 𠜱 0x20779 𠝹
|
||||
re := MustCompile("[𠜎-𠝹]", 0)
|
||||
|
||||
if m, err := re.MatchString("\u2070"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if m {
|
||||
t.Fatalf("Unexpected match")
|
||||
}
|
||||
|
||||
if m, err := re.MatchString("𠜱"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnicodeSupplementaryCharInRange(t *testing.T) {
|
||||
//0x2070E 0x20731 𠜱 0x20779 𠝹
|
||||
re := MustCompile(".", 0)
|
||||
|
||||
if m, err := re.MatchString("\u2070"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
if m, err := re.MatchString("𠜱"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnicodeScriptSets(t *testing.T) {
|
||||
re := MustCompile(`\p{Katakana}+`, 0)
|
||||
if m, err := re.MatchString("\u30A0\u30FF"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHexadecimalCurlyBraces(t *testing.T) {
|
||||
re := MustCompile(`\x20`, 0)
|
||||
if m, err := re.MatchString(" "); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
re = MustCompile(`\x{C4}`, 0)
|
||||
if m, err := re.MatchString("Ä"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
re = MustCompile(`\x{0C5}`, 0)
|
||||
if m, err := re.MatchString("Å"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
re = MustCompile(`\x{00C6}`, 0)
|
||||
if m, err := re.MatchString("Æ"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
re = MustCompile(`\x{1FF}`, 0)
|
||||
if m, err := re.MatchString("ǿ"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
re = MustCompile(`\x{02FF}`, 0)
|
||||
if m, err := re.MatchString("˿"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
re = MustCompile(`\x{1392}`, 0)
|
||||
if m, err := re.MatchString("᎒"); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
re = MustCompile(`\x{0010ffff}`, 0)
|
||||
if m, err := re.MatchString(string(0x10ffff)); err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
} else if !m {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
if _, err := Compile(`\x2R`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x0`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{2`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{2R`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{2R}`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{}`, 0); err == nil {
|
||||
t.Fatalf("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{10000`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{1234`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
if _, err := Compile(`\x{123456789}`, 0); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEmptyCharClass(t *testing.T) {
|
||||
if _, err := Compile("[]", 0); err == nil {
|
||||
t.Fatal("Empty char class isn't valid outside of ECMAScript mode")
|
||||
}
|
||||
}
|
||||
|
||||
func TestECMAEmptyCharClass(t *testing.T) {
|
||||
re := MustCompile("[]", ECMAScript)
|
||||
if m, err := re.MatchString("a"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m {
|
||||
t.Fatal("Expected no match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDot(t *testing.T) {
|
||||
re := MustCompile(".", 0)
|
||||
if m, err := re.MatchString("\r"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !m {
|
||||
t.Fatal("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestECMADot(t *testing.T) {
|
||||
re := MustCompile(".", ECMAScript)
|
||||
if m, err := re.MatchString("\r"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m {
|
||||
t.Fatal("Expected no match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecimalLookahead(t *testing.T) {
|
||||
re := MustCompile(`\1(A)`, 0)
|
||||
m, err := re.FindStringMatch("AA")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m != nil {
|
||||
t.Fatal("Expected no match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestECMADecimalLookahead(t *testing.T) {
|
||||
re := MustCompile(`\1(A)`, ECMAScript)
|
||||
m, err := re.FindStringMatch("AA")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c := m.GroupCount(); c != 2 {
|
||||
t.Fatalf("Group count !=2 (%d)", c)
|
||||
}
|
||||
|
||||
if s := m.GroupByNumber(0).String(); s != "A" {
|
||||
t.Fatalf("Group0 != 'A' ('%s')", s)
|
||||
}
|
||||
|
||||
if s := m.GroupByNumber(1).String(); s != "A" {
|
||||
t.Fatalf("Group1 != 'A' ('%s')", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestECMAOctal(t *testing.T) {
|
||||
re := MustCompile(`\100`, ECMAScript)
|
||||
if m, err := re.MatchString("@"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !m {
|
||||
t.Fatal("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegateRange(t *testing.T) {
|
||||
re := MustCompile(`[\D]`, 0)
|
||||
if m, err := re.MatchString("A"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !m {
|
||||
t.Fatal("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestECMANegateRange(t *testing.T) {
|
||||
re := MustCompile(`[\D]`, ECMAScript)
|
||||
if m, err := re.MatchString("A"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !m {
|
||||
t.Fatal("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestThreeByteUnicode_InputOnly(t *testing.T) {
|
||||
// confirm the bmprefix properly ignores 3-byte unicode in the input value
|
||||
// this used to panic
|
||||
re := MustCompile("高", 0)
|
||||
if m, err := re.MatchString("📍Test高"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !m {
|
||||
t.Fatal("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultibyteUnicode_MatchPartialPattern(t *testing.T) {
|
||||
re := MustCompile("猟な", 0)
|
||||
if m, err := re.MatchString("なあ🍺な"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m {
|
||||
t.Fatal("Expected no match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultibyteUnicode_Match(t *testing.T) {
|
||||
re := MustCompile("猟な", 0)
|
||||
if m, err := re.MatchString("なあ🍺猟な"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !m {
|
||||
t.Fatal("Expected match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternationNamedOptions_Errors(t *testing.T) {
|
||||
// all of these should give an error "error parsing regexp:"
|
||||
data := []string{
|
||||
"(?(?e))", "(?(?a)", "(?(?", "(?(", "?(a:b)", "?(a)", "?(a|b)", "?((a)", "?((a)a", "?((a)a|", "?((a)a|b",
|
||||
"(?(?i))", "(?(?I))", "(?(?m))", "(?(?M))", "(?(?s))", "(?(?S))", "(?(?x))", "(?(?X))", "(?(?n))", "(?(?N))", " (?(?n))",
|
||||
}
|
||||
for _, p := range data {
|
||||
re, err := Compile(p, 0)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, got nil")
|
||||
}
|
||||
if re != nil {
|
||||
t.Fatal("Expected unparsed regexp, got non-nil")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(err.Error(), "error parsing regexp: ") {
|
||||
t.Fatalf("Wanted parse error, got '%v'", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternationNamedOptions_Success(t *testing.T) {
|
||||
data := []struct {
|
||||
pattern string
|
||||
input string
|
||||
expectSuccess bool
|
||||
matchVal string
|
||||
}{
|
||||
{"(?(cat)|dog)", "cat", true, ""},
|
||||
{"(?(cat)|dog)", "catdog", true, ""},
|
||||
{"(?(cat)dog1|dog2)", "catdog1", false, ""},
|
||||
{"(?(cat)dog1|dog2)", "catdog2", true, "dog2"},
|
||||
{"(?(cat)dog1|dog2)", "catdog1dog2", true, "dog2"},
|
||||
{"(?(dog2))", "dog2", true, ""},
|
||||
{"(?(cat)|dog)", "oof", false, ""},
|
||||
{"(?(a:b))", "a", true, ""},
|
||||
{"(?(a:))", "a", true, ""},
|
||||
}
|
||||
for _, p := range data {
|
||||
re := MustCompile(p.pattern, 0)
|
||||
m, err := re.FindStringMatch(p.input)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error during match: %v", err)
|
||||
}
|
||||
if want, got := p.expectSuccess, m != nil; want != got {
|
||||
t.Fatalf("Success mismatch for %v, wanted %v, got %v", p.pattern, want, got)
|
||||
}
|
||||
if m != nil {
|
||||
if want, got := p.matchVal, m.String(); want != got {
|
||||
t.Fatalf("Match val mismatch for %v, wanted %v, got %v", p.pattern, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternationConstruct_Matches(t *testing.T) {
|
||||
re := MustCompile("(?(A)A123|C789)", 0)
|
||||
m, err := re.FindStringMatch("A123 B456 C789")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatal("Expected match, got nil")
|
||||
}
|
||||
|
||||
if want, got := "A123", m.String(); want != got {
|
||||
t.Fatalf("Wanted %v, got %v", want, got)
|
||||
}
|
||||
|
||||
m, err = re.FindNextMatch(m)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err in second match: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatal("Expected second match, got nil")
|
||||
}
|
||||
if want, got := "C789", m.String(); want != got {
|
||||
t.Fatalf("Wanted %v, got %v", want, got)
|
||||
}
|
||||
|
||||
m, err = re.FindNextMatch(m)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err in third match: %v", err)
|
||||
}
|
||||
if m != nil {
|
||||
t.Fatal("Did not expect third match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserFuzzCrashes(t *testing.T) {
|
||||
var crashes = []string{
|
||||
"(?'-", "(\\c0)", "(\\00(?())", "[\\p{0}", "(\x00?.*.()?(()?)?)*.x\xcb?&(\\s\x80)", "\\p{0}", "[0-[\\p{0}",
|
||||
}
|
||||
|
||||
for _, c := range crashes {
|
||||
t.Log(c)
|
||||
Compile(c, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserFuzzHangs(t *testing.T) {
|
||||
var hangs = []string{
|
||||
"\r{865720113}z\xd5{\r{861o", "\r{915355}\r{9153}", "\r{525005}", "\x01{19765625}", "(\r{068828256})", "\r{677525005}",
|
||||
}
|
||||
|
||||
for _, c := range hangs {
|
||||
t.Log(c)
|
||||
Compile(c, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParserPrefixLongLen(b *testing.B) {
|
||||
re := MustCompile("\r{100001}T+", 0)
|
||||
inp := strings.Repeat("testing", 10000) + strings.Repeat("\r", 100000) + "TTTT"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := re.MatchString(inp); err != nil {
|
||||
b.Fatalf("Unexpected err: %v", err)
|
||||
} else if m {
|
||||
b.Fatalf("Expected no match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestPcreStuff(t *testing.T) {
|
||||
re := MustCompile(`(?(?=(a))a)`, Debug)
|
||||
inp := unEscapeToMatch(`a`)
|
||||
fmt.Printf("Inp %q\n", inp)
|
||||
m, err := re.FindStringMatch(inp)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
|
||||
fmt.Printf("Match %s\n", m.dump())
|
||||
fmt.Printf("Text: %v\n", unEscapeGroup(m.String()))
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
//(.*)(\d+) different FirstChars ([\x00-\t\v-\x08] OR [\x00-\t\v-\uffff\p{Nd}]
|
|
@ -1,172 +0,0 @@
|
|||
package regexp2
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReplace_Basic(t *testing.T) {
|
||||
re := MustCompile(`test`, 0)
|
||||
str, err := re.Replace("this is a test", "unit", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "this is a unit", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_NamedGroup(t *testing.T) {
|
||||
re := MustCompile(`[^ ]+\s(?<time>)`, 0)
|
||||
str, err := re.Replace("08/10/99 16:00", "${time}", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "16:00", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_IgnoreCaseUpper(t *testing.T) {
|
||||
re := MustCompile(`dog`, IgnoreCase)
|
||||
str, err := re.Replace("my dog has fleas", "CAT", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "my CAT has fleas", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_IgnoreCaseLower(t *testing.T) {
|
||||
re := MustCompile(`olang`, IgnoreCase)
|
||||
str, err := re.Replace("GoLAnG", "olang", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "Golang", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_NumberGroup(t *testing.T) {
|
||||
re := MustCompile(`D\.(.+)`, None)
|
||||
str, err := re.Replace("D.Bau", "David $1", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "David Bau", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_LimitCount(t *testing.T) {
|
||||
re := MustCompile(`a`, None)
|
||||
str, err := re.Replace("aaaaa", "b", 0, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "bbaaa", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_LimitCountSlice(t *testing.T) {
|
||||
re := MustCompile(`a`, None)
|
||||
myStr := "aaaaa"
|
||||
str, err := re.Replace(myStr, "b", 3, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "aaabb", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_BeginBeforeAfterEnd(t *testing.T) {
|
||||
re := MustCompile(`a`, None)
|
||||
myStr := "a test a blah and a"
|
||||
str, err := re.Replace(myStr, "stuff", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "stuff test stuff blstuffh stuffnd stuff", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_BadSyntax(t *testing.T) {
|
||||
re := MustCompile(`a`, None)
|
||||
myStr := "this is a test"
|
||||
_, err := re.Replace(myStr, `$5000000000`, -1, -1)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceFunc_Basic(t *testing.T) {
|
||||
re := MustCompile(`test`, None)
|
||||
str, err := re.ReplaceFunc("this is a test", func(m Match) string { return "unit" }, -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "this is a unit", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceFunc_Multiple(t *testing.T) {
|
||||
re := MustCompile(`test`, None)
|
||||
count := 0
|
||||
str, err := re.ReplaceFunc("This test is another test for stuff", func(m Match) string {
|
||||
count++
|
||||
return strconv.Itoa(count)
|
||||
}, -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "This 1 is another 2 for stuff", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceFunc_Groups(t *testing.T) {
|
||||
re := MustCompile(`test(?<sub>ing)?`, None)
|
||||
count := 0
|
||||
str, err := re.ReplaceFunc("This testing is another test testingly junk", func(m Match) string {
|
||||
count++
|
||||
if m.GroupByName("sub").Length > 0 {
|
||||
// we have an "ing", make it negative
|
||||
return strconv.Itoa(count * -1)
|
||||
}
|
||||
return strconv.Itoa(count)
|
||||
}, -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "This -1 is another 2 -3ly junk", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_RefNumsDollarAmbiguous(t *testing.T) {
|
||||
re := MustCompile("(123)hello(789)", None)
|
||||
res, err := re.Replace("123hello789", "$1456$2", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, got := "$1456789", res; want != got {
|
||||
t.Fatalf("Wrong result: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_NestedGroups(t *testing.T) {
|
||||
re := MustCompile(`(\p{Sc}\s?)?(\d+\.?((?<=\.)\d+)?)(?(1)|\s?\p{Sc})?`, None)
|
||||
res, err := re.Replace("$17.43 €2 16.33 £0.98 0.43 £43 12€ 17", "$2", -1, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, got := "17.43 2 16.33 0.98 0.43 43 12 17", res; want != got {
|
||||
t.Fatalf("Wrong result: %s", got)
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package regexp2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRightToLeft_Basic(t *testing.T) {
|
||||
re := MustCompile(`foo\d+`, RightToLeft)
|
||||
s := "0123456789foo4567890foo1foo 0987"
|
||||
|
||||
m, err := re.FindStringMatch(s)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "foo1", m.String(); want != got {
|
||||
t.Fatalf("Match 0 failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
|
||||
m, err = re.FindNextMatch(m)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "foo4567890", m.String(); want != got {
|
||||
t.Fatalf("Match 1 failed, wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRightToLeft_StartAt(t *testing.T) {
|
||||
re := MustCompile(`\d`, RightToLeft)
|
||||
|
||||
m, err := re.FindStringMatchStartingAt("0123", -1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
if want, got := "3", m.String(); want != got {
|
||||
t.Fatalf("Find failed, wanted '%v', got '%v'", want, got)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRightToLeft_Replace(t *testing.T) {
|
||||
re := MustCompile(`\d`, RightToLeft)
|
||||
s := "0123456789foo4567890foo "
|
||||
str, err := re.Replace(s, "#", -1, 7)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
if want, got := "0123456789foo#######foo ", str; want != got {
|
||||
t.Fatalf("Replace failed, wanted '%v', got '%v'", want, got)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
[?.5x1fACc8E3'
|
|
@ -1 +0,0 @@
|
|||
\b\b\b\b\b\b\b\b\b
|
|
@ -1 +0,0 @@
|
|||
(?#)(?#)](?#)]?#)
|
|
@ -1 +0,0 @@
|
|||
\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\11(\1
|
|
@ -1 +0,0 @@
|
|||
(?-)(?-)
|
|
@ -1 +0,0 @@
|
|||
(?-----------------
|
|
@ -1 +0,0 @@
|
|||
((?'256'abc)\d+)?(?'16')(.*)
|
|
@ -1 +0,0 @@
|
|||
((((((){2147483647}((){2147483647}(){2147483647})){2147483647}))))
|
|
@ -1 +0,0 @@
|
|||
[\b\b\b\b\b\b
|
|
@ -1 +0,0 @@
|
|||
\D\D\D\D
|
|
@ -1 +0,0 @@
|
|||
(?I)іііііΉііΉіΉ
|
|
@ -1 +0,0 @@
|
|||
(){6,1}
|
|
@ -1,2 +0,0 @@
|
|||
((')'()'()'(')'()
|
||||
)
|
|
@ -1 +0,0 @@
|
|||
((){976})
|
|
@ -1 +0,0 @@
|
|||
[ケ-ケ-[[-ケ-[ケ]]][ケ-ケ-[[-ケ-[ケ]]]
|
|
@ -1 +0,0 @@
|
|||
\8090820312
|
|
@ -1 +0,0 @@
|
|||
(?=)((?=)(?=)(?=)(?=)(?=)(?=)(?=))(?=)(?=)(?=)(?=)(?=)(?=)(?=)(?=)(?=)(?=)
|
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
[cA2sx5fl7Uv_10)][cA2sx5fl7Uv_10]
|
|
@ -1 +0,0 @@
|
|||
("?e*"?e*
|
|
@ -1 +0,0 @@
|
|||
((()?)?)?(()?)?(()?)?(((()?)?)?)?(()?)?(((()?)?((()?)?)?(((()?)?)?(()?)?)?)?)?(()?)?((((()?)?)?)?)?
|
|
@ -1 +0,0 @@
|
|||
[\w\W]?]
|
|
@ -1 +0,0 @@
|
|||
(\6
|
|
@ -1 +0,0 @@
|
|||
(?'𠜎𠜎𠹝𠹝
|
|
@ -1 +0,0 @@
|
|||
(A|9)(A|9)(A|A||A|9)(A|9)(A|A||A(A|9)(A|A||A|9)(A|{Î)(A|A||A|9)|9)
|
|
@ -1 +0,0 @@
|
|||
[\t
|
|
@ -1 +0,0 @@
|
|||
((?'256'bc)\d+)?(?'16')(.)
|
|
@ -1 +0,0 @@
|
|||
{'{(
|
|
@ -1 +0,0 @@
|
|||
(?'-U'(?'-U'(?'-U'(?'-U'(?'U
|
|
@ -1 +0,0 @@
|
|||
(?'03
|
|
@ -1 +0,0 @@
|
|||
['-Q'-?'-Q'-?-''-Q'-?-n\n-''-/'-6-''-Q'-?-n\n-''-/'-6
|
|
@ -1 +0,0 @@
|
|||
[\u8333\u8f3a\u8f3a\u833a\u833a\u833a\u833a\u833a\u8f3a\u8333\u833a\u8f33
|
|
@ -1 +0,0 @@
|
|||
(?'U-6'(?'U-6'(?'U-6'(?'U-6'(?'6'(?'U-
|
|
@ -1 +0,0 @@
|
|||
(?n)()()(()(()()))()((())
|
|
@ -1 +0,0 @@
|
|||
(?I)[[-Ƹ][[-Ƹ][[-Ƹ]+[[-Ƹ]+[[-Ƹ][[-Ƹ]+
|
|
@ -1 +0,0 @@
|
|||
(?n)((@$)(@$))
|
|
@ -1 +0,0 @@
|
|||
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
|
|
@ -1 +0,0 @@
|
|||
(?)(?)(?)
|
|
@ -1 +0,0 @@
|
|||
(?I)(A9A7450580596923828125)
|
|
@ -1 +0,0 @@
|
|||
(?I)(.*\3826658AA)
|
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
((8((((((((((((((9(((((((((((((((((((((?'251(((((((((
|
|
@ -1 +0,0 @@
|
|||
\A\A\A\A\A\A\A(\A\A\A\A
|
|
@ -1 +0,0 @@
|
|||
[<5B>-<2D>-[<5B>-<2D>-[<5B>]]<5D>
|
|
@ -1 +0,0 @@
|
|||
(?#))(?#))(?#)((?#))(?#))(?#
|
|
@ -1 +0,0 @@
|
|||
(?!(?!(?k(?!(?!(?!
|
|
@ -1 +0,0 @@
|
|||
(c?]?]??]??`?]?`?]??]??`?]?`?]?)
|
|
@ -1 +0,0 @@
|
|||
(?=)
|
|
@ -1 +0,0 @@
|
|||
(?(?<=(?(?<=(?(?<=(?(?<=
|
|
@ -1 +0,0 @@
|
|||
(?x
|
|
@ -1 +0,0 @@
|
|||
[+](?#)([+](?#)
|
|
@ -1 +0,0 @@
|
|||
((?'6')+)?(?'6'.)
|
|
@ -1 +0,0 @@
|
|||
[\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\pp\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p}\p\p
|
|
@ -1 +0,0 @@
|
|||
(\16(.)
|
|
@ -1 +0,0 @@
|
|||
(?I)'''''invalid group name: group names must begin with a word character and have a matching terminator'
|
|
@ -1 +0,0 @@
|
|||
(?I)[RLOKQNGAXBWH][RLOKQNGAXBWH][RLOKQNGAXBWH][RLOKQNGAXBWH][RLOKQNGAXBWH][LOKNGH][ROQNGH][ONGAXBWH][RLOKQNGAXBWH][LOKNGAXBWH][LOKNGH][ROQNGH][ONGAXBWH][RLOKQNGAXBWH][LOKNGH][ROQNGAXBWH]
|
|
@ -1 +0,0 @@
|
|||
(?M)(^^^^^^^
|
|
@ -1 +0,0 @@
|
|||
(?n:(?I:(?I:(?I:(?I:(?I:(?I:(?I:(?I:
|
|
@ -1 +0,0 @@
|
|||
(()(())(())(())(()))()(())()((()(())(())(())(()))(()(())()(())(())(()()())(()))()(()())()()()(())
|
|
@ -1 +0,0 @@
|
|||
(?'e69(?'Call'(?'e69(?'Call
|
|
@ -1 +0,0 @@
|
|||
[[::][::][::][::][::][::][::]]
|
|
@ -1 +0,0 @@
|
|||
((|9|A|A|A|A|A|A|A|A|9|A|A|A|9)A|9|A|A|A|9)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue