1// Package internal contains common API functions and structures shared between lexer packages.
2package internal
3
4import (
5	"path/filepath"
6	"sort"
7	"strings"
8
9	"github.com/danwakefield/fnmatch"
10
11	"github.com/alecthomas/chroma"
12)
13
14// Registry of Lexers.
15var Registry = struct {
16	Lexers  chroma.Lexers
17	byName  map[string]chroma.Lexer
18	byAlias map[string]chroma.Lexer
19}{
20	byName:  map[string]chroma.Lexer{},
21	byAlias: map[string]chroma.Lexer{},
22}
23
24// Names of all lexers, optionally including aliases.
25func Names(withAliases bool) []string {
26	out := []string{}
27	for _, lexer := range Registry.Lexers {
28		config := lexer.Config()
29		out = append(out, config.Name)
30		if withAliases {
31			out = append(out, config.Aliases...)
32		}
33	}
34	sort.Strings(out)
35	return out
36}
37
38// Get a Lexer by name, alias or file extension.
39func Get(name string) chroma.Lexer {
40	if lexer := Registry.byName[name]; lexer != nil {
41		return lexer
42	}
43	if lexer := Registry.byAlias[name]; lexer != nil {
44		return lexer
45	}
46	if lexer := Registry.byName[strings.ToLower(name)]; lexer != nil {
47		return lexer
48	}
49	if lexer := Registry.byAlias[strings.ToLower(name)]; lexer != nil {
50		return lexer
51	}
52
53	candidates := chroma.PrioritisedLexers{}
54	// Try file extension.
55	if lexer := Match("filename." + name); lexer != nil {
56		candidates = append(candidates, lexer)
57	}
58	// Try exact filename.
59	if lexer := Match(name); lexer != nil {
60		candidates = append(candidates, lexer)
61	}
62	if len(candidates) == 0 {
63		return nil
64	}
65	sort.Sort(candidates)
66	return candidates[0]
67}
68
69// MatchMimeType attempts to find a lexer for the given MIME type.
70func MatchMimeType(mimeType string) chroma.Lexer {
71	matched := chroma.PrioritisedLexers{}
72	for _, l := range Registry.Lexers {
73		for _, lmt := range l.Config().MimeTypes {
74			if mimeType == lmt {
75				matched = append(matched, l)
76			}
77		}
78	}
79	if len(matched) != 0 {
80		sort.Sort(matched)
81		return matched[0]
82	}
83	return nil
84}
85
86// Match returns the first lexer matching filename.
87func Match(filename string) chroma.Lexer {
88	filename = filepath.Base(filename)
89	matched := chroma.PrioritisedLexers{}
90	// First, try primary filename matches.
91	for _, lexer := range Registry.Lexers {
92		config := lexer.Config()
93		for _, glob := range config.Filenames {
94			if fnmatch.Match(glob, filename, 0) {
95				matched = append(matched, lexer)
96			}
97		}
98	}
99	if len(matched) > 0 {
100		sort.Sort(matched)
101		return matched[0]
102	}
103	matched = nil
104	// Next, try filename aliases.
105	for _, lexer := range Registry.Lexers {
106		config := lexer.Config()
107		for _, glob := range config.AliasFilenames {
108			if fnmatch.Match(glob, filename, 0) {
109				matched = append(matched, lexer)
110			}
111		}
112	}
113	if len(matched) > 0 {
114		sort.Sort(matched)
115		return matched[0]
116	}
117	return nil
118}
119
120// Analyse text content and return the "best" lexer..
121func Analyse(text string) chroma.Lexer {
122	var picked chroma.Lexer
123	highest := float32(0.0)
124	for _, lexer := range Registry.Lexers {
125		if analyser, ok := lexer.(chroma.Analyser); ok {
126			weight := analyser.AnalyseText(text)
127			if weight > highest {
128				picked = lexer
129				highest = weight
130			}
131		}
132	}
133	return picked
134}
135
136// Register a Lexer with the global registry.
137func Register(lexer chroma.Lexer) chroma.Lexer {
138	config := lexer.Config()
139	Registry.byName[config.Name] = lexer
140	Registry.byName[strings.ToLower(config.Name)] = lexer
141	for _, alias := range config.Aliases {
142		Registry.byAlias[alias] = lexer
143		Registry.byAlias[strings.ToLower(alias)] = lexer
144	}
145	Registry.Lexers = append(Registry.Lexers, lexer)
146	return lexer
147}
148
149// PlaintextRules is used for the fallback lexer as well as the explicit
150// plaintext lexer.
151func PlaintextRules() chroma.Rules {
152	return chroma.Rules{
153		"root": []chroma.Rule{
154			{`.+`, chroma.Text, nil},
155			{`\n`, chroma.Text, nil},
156		},
157	}
158}
159
160// Fallback lexer if no other is found.
161var Fallback chroma.Lexer = chroma.MustNewLazyLexer(&chroma.Config{
162	Name:      "fallback",
163	Filenames: []string{"*"},
164}, PlaintextRules)
165