1// Package internal contains common API functions and structures shared between lexer packages.
2package internal
3
4import (
5 "path/filepath"
6 "sort"
7 "strings"
8
9 "github.com/danwakefield/fnmatch"
10
11 "github.com/alecthomas/chroma"
12)
13
14// Registry of Lexers.
15var Registry = struct {
16 Lexers chroma.Lexers
17 byName map[string]chroma.Lexer
18 byAlias map[string]chroma.Lexer
19}{
20 byName: map[string]chroma.Lexer{},
21 byAlias: map[string]chroma.Lexer{},
22}
23
24// Names of all lexers, optionally including aliases.
25func Names(withAliases bool) []string {
26 out := []string{}
27 for _, lexer := range Registry.Lexers {
28 config := lexer.Config()
29 out = append(out, config.Name)
30 if withAliases {
31 out = append(out, config.Aliases...)
32 }
33 }
34 sort.Strings(out)
35 return out
36}
37
38// Get a Lexer by name, alias or file extension.
39func Get(name string) chroma.Lexer {
40 candidates := chroma.PrioritisedLexers{}
41 if lexer := Registry.byName[name]; lexer != nil {
42 candidates = append(candidates, lexer)
43 }
44 if lexer := Registry.byAlias[name]; lexer != nil {
45 candidates = append(candidates, lexer)
46 }
47 if lexer := Registry.byName[strings.ToLower(name)]; lexer != nil {
48 candidates = append(candidates, lexer)
49 }
50 if lexer := Registry.byAlias[strings.ToLower(name)]; lexer != nil {
51 candidates = append(candidates, lexer)
52 }
53 // Try file extension.
54 if lexer := Match("filename." + name); lexer != nil {
55 candidates = append(candidates, lexer)
56 }
57 // Try exact filename.
58 if lexer := Match(name); lexer != nil {
59 candidates = append(candidates, lexer)
60 }
61 if len(candidates) == 0 {
62 return nil
63 }
64 sort.Sort(candidates)
65 return candidates[0]
66}
67
68// MatchMimeType attempts to find a lexer for the given MIME type.
69func MatchMimeType(mimeType string) chroma.Lexer {
70 matched := chroma.PrioritisedLexers{}
71 for _, l := range Registry.Lexers {
72 for _, lmt := range l.Config().MimeTypes {
73 if mimeType == lmt {
74 matched = append(matched, l)
75 }
76 }
77 }
78 if len(matched) != 0 {
79 sort.Sort(matched)
80 return matched[0]
81 }
82 return nil
83}
84
85// Match returns the first lexer matching filename.
86func Match(filename string) chroma.Lexer {
87 filename = filepath.Base(filename)
88 matched := chroma.PrioritisedLexers{}
89 // First, try primary filename matches.
90 for _, lexer := range Registry.Lexers {
91 config := lexer.Config()
92 for _, glob := range config.Filenames {
93 if fnmatch.Match(glob, filename, 0) {
94 matched = append(matched, lexer)
95 }
96 }
97 }
98 if len(matched) > 0 {
99 sort.Sort(matched)
100 return matched[0]
101 }
102 matched = nil
103 // Next, try filename aliases.
104 for _, lexer := range Registry.Lexers {
105 config := lexer.Config()
106 for _, glob := range config.AliasFilenames {
107 if fnmatch.Match(glob, filename, 0) {
108 matched = append(matched, lexer)
109 }
110 }
111 }
112 if len(matched) > 0 {
113 sort.Sort(matched)
114 return matched[0]
115 }
116 return nil
117}
118
119// Analyse text content and return the "best" lexer..
120func Analyse(text string) chroma.Lexer {
121 var picked chroma.Lexer
122 highest := float32(0.0)
123 for _, lexer := range Registry.Lexers {
124 if analyser, ok := lexer.(chroma.Analyser); ok {
125 weight := analyser.AnalyseText(text)
126 if weight > highest {
127 picked = lexer
128 highest = weight
129 }
130 }
131 }
132 return picked
133}
134
135// Register a Lexer with the global registry.
136func Register(lexer chroma.Lexer) chroma.Lexer {
137 config := lexer.Config()
138 Registry.byName[config.Name] = lexer
139 Registry.byName[strings.ToLower(config.Name)] = lexer
140 for _, alias := range config.Aliases {
141 Registry.byAlias[alias] = lexer
142 Registry.byAlias[strings.ToLower(alias)] = lexer
143 }
144 Registry.Lexers = append(Registry.Lexers, lexer)
145 return lexer
146}
147
148// Used for the fallback lexer as well as the explicit plaintext lexer
149var PlaintextRules = chroma.Rules{
150 "root": []chroma.Rule{
151 {`.+`, chroma.Text, nil},
152 {`\n`, chroma.Text, nil},
153 },
154}
155
156// Fallback lexer if no other is found.
157var Fallback chroma.Lexer = chroma.MustNewLexer(&chroma.Config{
158 Name: "fallback",
159 Filenames: []string{"*"},
160}, PlaintextRules)