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}