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/alecthomas/chroma" 10) 11 12var ( 13 ignoredSuffixes = [...]string{ 14 // Editor backups 15 "~", ".bak", ".old", ".orig", 16 // Debian and derivatives apt/dpkg/ucf backups 17 ".dpkg-dist", ".dpkg-old", ".ucf-dist", ".ucf-new", ".ucf-old", 18 // Red Hat and derivatives rpm backups 19 ".rpmnew", ".rpmorig", ".rpmsave", 20 // Build system input/template files 21 ".in", 22 } 23) 24 25// Registry of Lexers. 26var Registry = struct { 27 Lexers chroma.Lexers 28 byName map[string]chroma.Lexer 29 byAlias map[string]chroma.Lexer 30}{ 31 byName: map[string]chroma.Lexer{}, 32 byAlias: map[string]chroma.Lexer{}, 33} 34 35// Names of all lexers, optionally including aliases. 36func Names(withAliases bool) []string { 37 out := []string{} 38 for _, lexer := range Registry.Lexers { 39 config := lexer.Config() 40 out = append(out, config.Name) 41 if withAliases { 42 out = append(out, config.Aliases...) 43 } 44 } 45 sort.Strings(out) 46 return out 47} 48 49// Get a Lexer by name, alias or file extension. 50func Get(name string) chroma.Lexer { 51 if lexer := Registry.byName[name]; lexer != nil { 52 return lexer 53 } 54 if lexer := Registry.byAlias[name]; lexer != nil { 55 return lexer 56 } 57 if lexer := Registry.byName[strings.ToLower(name)]; lexer != nil { 58 return lexer 59 } 60 if lexer := Registry.byAlias[strings.ToLower(name)]; lexer != nil { 61 return lexer 62 } 63 64 candidates := chroma.PrioritisedLexers{} 65 // Try file extension. 66 if lexer := Match("filename." + name); lexer != nil { 67 candidates = append(candidates, lexer) 68 } 69 // Try exact filename. 70 if lexer := Match(name); lexer != nil { 71 candidates = append(candidates, lexer) 72 } 73 if len(candidates) == 0 { 74 return nil 75 } 76 sort.Sort(candidates) 77 return candidates[0] 78} 79 80// MatchMimeType attempts to find a lexer for the given MIME type. 81func MatchMimeType(mimeType string) chroma.Lexer { 82 matched := chroma.PrioritisedLexers{} 83 for _, l := range Registry.Lexers { 84 for _, lmt := range l.Config().MimeTypes { 85 if mimeType == lmt { 86 matched = append(matched, l) 87 } 88 } 89 } 90 if len(matched) != 0 { 91 sort.Sort(matched) 92 return matched[0] 93 } 94 return nil 95} 96 97// Match returns the first lexer matching filename. 98func Match(filename string) chroma.Lexer { 99 filename = filepath.Base(filename) 100 matched := chroma.PrioritisedLexers{} 101 // First, try primary filename matches. 102 for _, lexer := range Registry.Lexers { 103 config := lexer.Config() 104 for _, glob := range config.Filenames { 105 ok, err := filepath.Match(glob, filename) 106 if err != nil { // nolint 107 panic(err) 108 } else if ok { 109 matched = append(matched, lexer) 110 } else { 111 for _, suf := range &ignoredSuffixes { 112 ok, err := filepath.Match(glob+suf, filename) 113 if err != nil { 114 panic(err) 115 } else if ok { 116 matched = append(matched, lexer) 117 break 118 } 119 } 120 } 121 } 122 } 123 if len(matched) > 0 { 124 sort.Sort(matched) 125 return matched[0] 126 } 127 matched = nil 128 // Next, try filename aliases. 129 for _, lexer := range Registry.Lexers { 130 config := lexer.Config() 131 for _, glob := range config.AliasFilenames { 132 ok, err := filepath.Match(glob, filename) 133 if err != nil { // nolint 134 panic(err) 135 } else if ok { 136 matched = append(matched, lexer) 137 } else { 138 for _, suf := range &ignoredSuffixes { 139 ok, err := filepath.Match(glob+suf, filename) 140 if err != nil { 141 panic(err) 142 } else if ok { 143 matched = append(matched, lexer) 144 break 145 } 146 } 147 } 148 } 149 } 150 if len(matched) > 0 { 151 sort.Sort(matched) 152 return matched[0] 153 } 154 return nil 155} 156 157// Analyse text content and return the "best" lexer.. 158func Analyse(text string) chroma.Lexer { 159 var picked chroma.Lexer 160 highest := float32(0.0) 161 for _, lexer := range Registry.Lexers { 162 if analyser, ok := lexer.(chroma.Analyser); ok { 163 weight := analyser.AnalyseText(text) 164 if weight > highest { 165 picked = lexer 166 highest = weight 167 } 168 } 169 } 170 return picked 171} 172 173// Register a Lexer with the global registry. 174func Register(lexer chroma.Lexer) chroma.Lexer { 175 config := lexer.Config() 176 Registry.byName[config.Name] = lexer 177 Registry.byName[strings.ToLower(config.Name)] = lexer 178 for _, alias := range config.Aliases { 179 Registry.byAlias[alias] = lexer 180 Registry.byAlias[strings.ToLower(alias)] = lexer 181 } 182 Registry.Lexers = append(Registry.Lexers, lexer) 183 return lexer 184} 185 186// PlaintextRules is used for the fallback lexer as well as the explicit 187// plaintext lexer. 188func PlaintextRules() chroma.Rules { 189 return chroma.Rules{ 190 "root": []chroma.Rule{ 191 {`.+`, chroma.Text, nil}, 192 {`\n`, chroma.Text, nil}, 193 }, 194 } 195} 196 197// Fallback lexer if no other is found. 198var Fallback chroma.Lexer = chroma.MustNewLazyLexer(&chroma.Config{ 199 Name: "fallback", 200 Filenames: []string{"*"}, 201}, PlaintextRules) 202