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// Get a Lexer by name, alias or file extension.
52func (l *LexerRegistry) Get(name string) Lexer {
53 if lexer := l.byName[name]; lexer != nil {
54 return lexer
55 }
56 if lexer := l.byAlias[name]; lexer != nil {
57 return lexer
58 }
59 if lexer := l.byName[strings.ToLower(name)]; lexer != nil {
60 return lexer
61 }
62 if lexer := l.byAlias[strings.ToLower(name)]; lexer != nil {
63 return lexer
64 }
65
66 candidates := PrioritisedLexers{}
67 // Try file extension.
68 if lexer := l.Match("filename." + name); lexer != nil {
69 candidates = append(candidates, lexer)
70 }
71 // Try exact filename.
72 if lexer := l.Match(name); lexer != nil {
73 candidates = append(candidates, lexer)
74 }
75 if len(candidates) == 0 {
76 return nil
77 }
78 sort.Sort(candidates)
79 return candidates[0]
80}
81
82// MatchMimeType attempts to find a lexer for the given MIME type.
83func (l *LexerRegistry) MatchMimeType(mimeType string) Lexer {
84 matched := PrioritisedLexers{}
85 for _, l := range l.Lexers {
86 for _, lmt := range l.Config().MimeTypes {
87 if mimeType == lmt {
88 matched = append(matched, l)
89 }
90 }
91 }
92 if len(matched) != 0 {
93 sort.Sort(matched)
94 return matched[0]
95 }
96 return nil
97}
98
99// Match returns the first lexer matching filename.
100//
101// Note that this iterates over all file patterns in all lexers, so is not fast.
102func (l *LexerRegistry) Match(filename string) Lexer {
103 filename = filepath.Base(filename)
104 matched := PrioritisedLexers{}
105 // First, try primary filename matches.
106 for _, lexer := range l.Lexers {
107 config := lexer.Config()
108 for _, glob := range config.Filenames {
109 ok, err := filepath.Match(glob, filename)
110 if err != nil { // nolint
111 panic(err)
112 } else if ok {
113 matched = append(matched, lexer)
114 } else {
115 for _, suf := range &ignoredSuffixes {
116 ok, err := filepath.Match(glob+suf, filename)
117 if err != nil {
118 panic(err)
119 } else if ok {
120 matched = append(matched, lexer)
121 break
122 }
123 }
124 }
125 }
126 }
127 if len(matched) > 0 {
128 sort.Sort(matched)
129 return matched[0]
130 }
131 matched = nil
132 // Next, try filename aliases.
133 for _, lexer := range l.Lexers {
134 config := lexer.Config()
135 for _, glob := range config.AliasFilenames {
136 ok, err := filepath.Match(glob, filename)
137 if err != nil { // nolint
138 panic(err)
139 } else if ok {
140 matched = append(matched, lexer)
141 } else {
142 for _, suf := range &ignoredSuffixes {
143 ok, err := filepath.Match(glob+suf, filename)
144 if err != nil {
145 panic(err)
146 } else if ok {
147 matched = append(matched, lexer)
148 break
149 }
150 }
151 }
152 }
153 }
154 if len(matched) > 0 {
155 sort.Sort(matched)
156 return matched[0]
157 }
158 return nil
159}
160
161// Analyse text content and return the "best" lexer..
162func (l *LexerRegistry) Analyse(text string) Lexer {
163 var picked Lexer
164 highest := float32(0.0)
165 for _, lexer := range l.Lexers {
166 if analyser, ok := lexer.(Analyser); ok {
167 weight := analyser.AnalyseText(text)
168 if weight > highest {
169 picked = lexer
170 highest = weight
171 }
172 }
173 }
174 return picked
175}
176
177// Register a Lexer with the LexerRegistry. If the lexer is already registered
178// it will be replaced.
179func (l *LexerRegistry) Register(lexer Lexer) Lexer {
180 lexer.SetRegistry(l)
181 config := lexer.Config()
182
183 l.byName[config.Name] = lexer
184 l.byName[strings.ToLower(config.Name)] = lexer
185
186 for _, alias := range config.Aliases {
187 l.byAlias[alias] = lexer
188 l.byAlias[strings.ToLower(alias)] = lexer
189 }
190
191 l.Lexers = add(l.Lexers, lexer)
192
193 return lexer
194}
195
196// add adds a lexer to a slice of lexers if it doesn't already exist, or if found will replace it.
197func add(lexers Lexers, lexer Lexer) Lexers {
198 for i, val := range lexers {
199 if val == nil {
200 continue
201 }
202
203 if val.Config().Name == lexer.Config().Name {
204 lexers[i] = lexer
205 return lexers
206 }
207 }
208
209 return append(lexers, lexer)
210}