registry.go

  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}