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.
100func (l *LexerRegistry) Match(filename string) Lexer {
101	filename = filepath.Base(filename)
102	matched := PrioritisedLexers{}
103	// First, try primary filename matches.
104	for _, lexer := range l.Lexers {
105		config := lexer.Config()
106		for _, glob := range config.Filenames {
107			ok, err := filepath.Match(glob, filename)
108			if err != nil { // nolint
109				panic(err)
110			} else if ok {
111				matched = append(matched, lexer)
112			} else {
113				for _, suf := range &ignoredSuffixes {
114					ok, err := filepath.Match(glob+suf, filename)
115					if err != nil {
116						panic(err)
117					} else if ok {
118						matched = append(matched, lexer)
119						break
120					}
121				}
122			}
123		}
124	}
125	if len(matched) > 0 {
126		sort.Sort(matched)
127		return matched[0]
128	}
129	matched = nil
130	// Next, try filename aliases.
131	for _, lexer := range l.Lexers {
132		config := lexer.Config()
133		for _, glob := range config.AliasFilenames {
134			ok, err := filepath.Match(glob, filename)
135			if err != nil { // nolint
136				panic(err)
137			} else if ok {
138				matched = append(matched, lexer)
139			} else {
140				for _, suf := range &ignoredSuffixes {
141					ok, err := filepath.Match(glob+suf, filename)
142					if err != nil {
143						panic(err)
144					} else if ok {
145						matched = append(matched, lexer)
146						break
147					}
148				}
149			}
150		}
151	}
152	if len(matched) > 0 {
153		sort.Sort(matched)
154		return matched[0]
155	}
156	return nil
157}
158
159// Analyse text content and return the "best" lexer..
160func (l *LexerRegistry) Analyse(text string) Lexer {
161	var picked Lexer
162	highest := float32(0.0)
163	for _, lexer := range l.Lexers {
164		if analyser, ok := lexer.(Analyser); ok {
165			weight := analyser.AnalyseText(text)
166			if weight > highest {
167				picked = lexer
168				highest = weight
169			}
170		}
171	}
172	return picked
173}
174
175// Register a Lexer with the LexerRegistry.
176func (l *LexerRegistry) Register(lexer Lexer) Lexer {
177	lexer.SetRegistry(l)
178	config := lexer.Config()
179	l.byName[config.Name] = lexer
180	l.byName[strings.ToLower(config.Name)] = lexer
181	for _, alias := range config.Aliases {
182		l.byAlias[alias] = lexer
183		l.byAlias[strings.ToLower(alias)] = lexer
184	}
185	l.Lexers = append(l.Lexers, lexer)
186	return lexer
187}