api.go

  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	candidates := chroma.PrioritisedLexers{}
 41	if lexer := Registry.byName[name]; lexer != nil {
 42		candidates = append(candidates, lexer)
 43	}
 44	if lexer := Registry.byAlias[name]; lexer != nil {
 45		candidates = append(candidates, lexer)
 46	}
 47	if lexer := Registry.byName[strings.ToLower(name)]; lexer != nil {
 48		candidates = append(candidates, lexer)
 49	}
 50	if lexer := Registry.byAlias[strings.ToLower(name)]; lexer != nil {
 51		candidates = append(candidates, lexer)
 52	}
 53	// Try file extension.
 54	if lexer := Match("filename." + name); lexer != nil {
 55		candidates = append(candidates, lexer)
 56	}
 57	// Try exact filename.
 58	if lexer := Match(name); lexer != nil {
 59		candidates = append(candidates, lexer)
 60	}
 61	if len(candidates) == 0 {
 62		return nil
 63	}
 64	sort.Sort(candidates)
 65	return candidates[0]
 66}
 67
 68// MatchMimeType attempts to find a lexer for the given MIME type.
 69func MatchMimeType(mimeType string) chroma.Lexer {
 70	matched := chroma.PrioritisedLexers{}
 71	for _, l := range Registry.Lexers {
 72		for _, lmt := range l.Config().MimeTypes {
 73			if mimeType == lmt {
 74				matched = append(matched, l)
 75			}
 76		}
 77	}
 78	if len(matched) != 0 {
 79		sort.Sort(matched)
 80		return matched[0]
 81	}
 82	return nil
 83}
 84
 85// Match returns the first lexer matching filename.
 86func Match(filename string) chroma.Lexer {
 87	filename = filepath.Base(filename)
 88	matched := chroma.PrioritisedLexers{}
 89	// First, try primary filename matches.
 90	for _, lexer := range Registry.Lexers {
 91		config := lexer.Config()
 92		for _, glob := range config.Filenames {
 93			if fnmatch.Match(glob, filename, 0) {
 94				matched = append(matched, lexer)
 95			}
 96		}
 97	}
 98	if len(matched) > 0 {
 99		sort.Sort(matched)
100		return matched[0]
101	}
102	matched = nil
103	// Next, try filename aliases.
104	for _, lexer := range Registry.Lexers {
105		config := lexer.Config()
106		for _, glob := range config.AliasFilenames {
107			if fnmatch.Match(glob, filename, 0) {
108				matched = append(matched, lexer)
109			}
110		}
111	}
112	if len(matched) > 0 {
113		sort.Sort(matched)
114		return matched[0]
115	}
116	return nil
117}
118
119// Analyse text content and return the "best" lexer..
120func Analyse(text string) chroma.Lexer {
121	var picked chroma.Lexer
122	highest := float32(0.0)
123	for _, lexer := range Registry.Lexers {
124		if analyser, ok := lexer.(chroma.Analyser); ok {
125			weight := analyser.AnalyseText(text)
126			if weight > highest {
127				picked = lexer
128				highest = weight
129			}
130		}
131	}
132	return picked
133}
134
135// Register a Lexer with the global registry.
136func Register(lexer chroma.Lexer) chroma.Lexer {
137	config := lexer.Config()
138	Registry.byName[config.Name] = lexer
139	Registry.byName[strings.ToLower(config.Name)] = lexer
140	for _, alias := range config.Aliases {
141		Registry.byAlias[alias] = lexer
142		Registry.byAlias[strings.ToLower(alias)] = lexer
143	}
144	Registry.Lexers = append(Registry.Lexers, lexer)
145	return lexer
146}
147
148// Used for the fallback lexer as well as the explicit plaintext lexer
149var PlaintextRules = chroma.Rules{
150	"root": []chroma.Rule{
151		{`.+`, chroma.Text, nil},
152		{`\n`, chroma.Text, nil},
153	},
154}
155
156// Fallback lexer if no other is found.
157var Fallback chroma.Lexer = chroma.MustNewLexer(&chroma.Config{
158	Name:      "fallback",
159	Filenames: []string{"*"},
160}, PlaintextRules)