1package chroma
2
3import (
4 "path/filepath"
5 "sort"
6 "strings"
7)
8
9var (
10 ignoredSuffixes = [...]string{
11 // Editor backups
12 "~", ".bak", ".old", ".orig",
13 // Debian and derivatives apt/dpkg/ucf backups
14 ".dpkg-dist", ".dpkg-old", ".ucf-dist", ".ucf-new", ".ucf-old",
15 // Red Hat and derivatives rpm backups
16 ".rpmnew", ".rpmorig", ".rpmsave",
17 // Build system input/template files
18 ".in",
19 }
20)
21
22// LexerRegistry is a registry of Lexers.
23type LexerRegistry struct {
24 Lexers Lexers
25 byName map[string]Lexer
26 byAlias map[string]Lexer
27}
28
29// NewLexerRegistry creates a new LexerRegistry of Lexers.
30func NewLexerRegistry() *LexerRegistry {
31 return &LexerRegistry{
32 byName: map[string]Lexer{},
33 byAlias: map[string]Lexer{},
34 }
35}
36
37// Names of all lexers, optionally including aliases.
38func (l *LexerRegistry) Names(withAliases bool) []string {
39 out := []string{}
40 for _, lexer := range l.Lexers {
41 config := lexer.Config()
42 out = append(out, config.Name)
43 if withAliases {
44 out = append(out, config.Aliases...)
45 }
46 }
47 sort.Strings(out)
48 return out
49}
50
51// Aliases of all the lexers, and skip those lexers who do not have any aliases,
52// or show their name instead
53func (l *LexerRegistry) Aliases(skipWithoutAliases bool) []string {
54 out := []string{}
55 for _, lexer := range l.Lexers {
56 config := lexer.Config()
57 if len(config.Aliases) == 0 {
58 if skipWithoutAliases {
59 continue
60 }
61 out = append(out, config.Name)
62 }
63 out = append(out, config.Aliases...)
64 }
65 sort.Strings(out)
66 return out
67}
68
69// Get a Lexer by name, alias or file extension.
70func (l *LexerRegistry) Get(name string) Lexer {
71 if lexer := l.byName[name]; lexer != nil {
72 return lexer
73 }
74 if lexer := l.byAlias[name]; lexer != nil {
75 return lexer
76 }
77 if lexer := l.byName[strings.ToLower(name)]; lexer != nil {
78 return lexer
79 }
80 if lexer := l.byAlias[strings.ToLower(name)]; lexer != nil {
81 return lexer
82 }
83
84 candidates := PrioritisedLexers{}
85 // Try file extension.
86 if lexer := l.Match("filename." + name); lexer != nil {
87 candidates = append(candidates, lexer)
88 }
89 // Try exact filename.
90 if lexer := l.Match(name); lexer != nil {
91 candidates = append(candidates, lexer)
92 }
93 if len(candidates) == 0 {
94 return nil
95 }
96 sort.Sort(candidates)
97 return candidates[0]
98}
99
100// MatchMimeType attempts to find a lexer for the given MIME type.
101func (l *LexerRegistry) MatchMimeType(mimeType string) Lexer {
102 matched := PrioritisedLexers{}
103 for _, l := range l.Lexers {
104 for _, lmt := range l.Config().MimeTypes {
105 if mimeType == lmt {
106 matched = append(matched, l)
107 }
108 }
109 }
110 if len(matched) != 0 {
111 sort.Sort(matched)
112 return matched[0]
113 }
114 return nil
115}
116
117// Match returns the first lexer matching filename.
118//
119// Note that this iterates over all file patterns in all lexers, so is not fast.
120func (l *LexerRegistry) Match(filename string) Lexer {
121 filename = filepath.Base(filename)
122 matched := PrioritisedLexers{}
123 // First, try primary filename matches.
124 for _, lexer := range l.Lexers {
125 config := lexer.Config()
126 for _, glob := range config.Filenames {
127 ok, err := filepath.Match(glob, filename)
128 if err != nil { // nolint
129 panic(err)
130 } else if ok {
131 matched = append(matched, lexer)
132 } else {
133 for _, suf := range &ignoredSuffixes {
134 ok, err := filepath.Match(glob+suf, filename)
135 if err != nil {
136 panic(err)
137 } else if ok {
138 matched = append(matched, lexer)
139 break
140 }
141 }
142 }
143 }
144 }
145 if len(matched) > 0 {
146 sort.Sort(matched)
147 return matched[0]
148 }
149 matched = nil
150 // Next, try filename aliases.
151 for _, lexer := range l.Lexers {
152 config := lexer.Config()
153 for _, glob := range config.AliasFilenames {
154 ok, err := filepath.Match(glob, filename)
155 if err != nil { // nolint
156 panic(err)
157 } else if ok {
158 matched = append(matched, lexer)
159 } else {
160 for _, suf := range &ignoredSuffixes {
161 ok, err := filepath.Match(glob+suf, filename)
162 if err != nil {
163 panic(err)
164 } else if ok {
165 matched = append(matched, lexer)
166 break
167 }
168 }
169 }
170 }
171 }
172 if len(matched) > 0 {
173 sort.Sort(matched)
174 return matched[0]
175 }
176 return nil
177}
178
179// Analyse text content and return the "best" lexer..
180func (l *LexerRegistry) Analyse(text string) Lexer {
181 var picked Lexer
182 highest := float32(0.0)
183 for _, lexer := range l.Lexers {
184 if analyser, ok := lexer.(Analyser); ok {
185 weight := analyser.AnalyseText(text)
186 if weight > highest {
187 picked = lexer
188 highest = weight
189 }
190 }
191 }
192 return picked
193}
194
195// Register a Lexer with the LexerRegistry. If the lexer is already registered
196// it will be replaced.
197func (l *LexerRegistry) Register(lexer Lexer) Lexer {
198 lexer.SetRegistry(l)
199 config := lexer.Config()
200
201 l.byName[config.Name] = lexer
202 l.byName[strings.ToLower(config.Name)] = lexer
203
204 for _, alias := range config.Aliases {
205 l.byAlias[alias] = lexer
206 l.byAlias[strings.ToLower(alias)] = lexer
207 }
208
209 l.Lexers = add(l.Lexers, lexer)
210
211 return lexer
212}
213
214// add adds a lexer to a slice of lexers if it doesn't already exist, or if found will replace it.
215func add(lexers Lexers, lexer Lexer) Lexers {
216 for i, val := range lexers {
217 if val == nil {
218 continue
219 }
220
221 if val.Config().Name == lexer.Config().Name {
222 lexers[i] = lexer
223 return lexers
224 }
225 }
226
227 return append(lexers, lexer)
228}