registry.ex (7623B)
1 defmodule Makeup.Registry do 2 @moduledoc """ 3 A registry that allows users to dynamically register new makeup lexers. 4 5 Lexers should register themselves on application start. 6 That way, you can add support for new programming languages by depending on the relevant lexers. 7 This is useful for projects such as ExDoc, which might contain code 8 in a number of different programming languages. 9 """ 10 11 @name_registry_key :lexer_name_registry 12 13 @extension_registry_key :lexer_extension_registry 14 15 # -------------------------------------------------------------------------- 16 # Public API 17 # -------------------------------------------------------------------------- 18 19 @doc """ 20 Gets the list of supported language names. 21 """ 22 def supported_language_names() do 23 Map.keys(get_name_registry()) 24 end 25 26 @doc """ 27 Gets the list of supported language extensions. 28 """ 29 def supported_file_extensions() do 30 Map.keys(get_extension_registry()) 31 end 32 33 @doc """ 34 Adds a new lexer to Makeup's registry under the given `name`. 35 36 This function expects a language name (e.g. `"elixir"`) and a pair containing 37 a `lexer` and a list of `options`. 38 39 You might want to use the `Makeup.Registry.register_lexer/2` function instead. 40 41 ## Examples 42 43 alias Makeup.Lexers.ElixirLexer 44 alias Makeup.Registry 45 46 Registry.register_lexer_with_name("elixir", {ElixirLexer, []}) 47 Registry.register_lexer_with_name("iex", {ElixirLexer, []}) 48 """ 49 def register_lexer_with_name(name, {lexer, options}) when is_binary(name) do 50 old_registry = get_name_registry() 51 updated_registry = Map.put(old_registry, name, {lexer, options}) 52 put_name_registry(updated_registry) 53 end 54 55 @doc """ 56 Adds a new lexer to Makeup's registry under the given `extension`. 57 58 This function expects a file extension (e.g. `"ex"`) and a pair containing 59 a `lexer` and a list of `options`. 60 61 You might want to use the `Makeup.Registry.register_lexer/2` function instead. 62 63 ## Examples 64 65 alias Makeup.Lexers.ElixirLexer 66 alias Makeup.Registry 67 68 Registry.register_lexer_with_extension("ex"), {ElixirLexer, []}) 69 Registry.register_lexer_with_extension("exs"), {ElixirLexer, []}) 70 """ 71 def register_lexer_with_extension(name, {lexer, options}) when is_binary(name) do 72 old_registry = get_extension_registry() 73 updated_registry = Map.put(old_registry, name, {lexer, options}) 74 put_extension_registry(updated_registry) 75 end 76 77 @doc """ 78 Add a new lexer to Makeup's registry under the given names and extensions. 79 80 Expects a lexer `lexer` and a number of options: 81 82 * `:options` (default: `[]`) - the lexer options. 83 If your lexer doesn't take any options, you'll want the default value of `[]`. 84 85 * `:names` (default: `[]`) - a list of strings with the language names for the lexer. 86 Language names are strings, not atoms. 87 Even if there is only one valid name, you must supply a list with that name. 88 To avoid filling the registry unnecessarily, you should normalize your language names 89 to lowercase strings. 90 If the caller wants to support upper case language names for some reason, 91 they can normalize the language names themselves. 92 93 * `:extensions` (default: `[]`) - the list of file extensions for the languages supported by the lexer. 94 For example, the elixir lexer should support the `"ex"` and `"exs"` file extensions. 95 The extensions should not include the dot. 96 That is, you should register `"ex"` and not `".ex"`. 97 Even if there is only a supported extension, you must supply a list. 98 99 ## Example 100 101 alias Makeup.Registry 102 alias Makeup.Lexers.ElixirLexer 103 # The `:options` key is not required 104 Registry.register_lexer(ElixirLexer, names: ["elixir", "iex"], extensions: ["ex", "exs"]) 105 106 """ 107 def register_lexer(lexer, opts) do 108 options = Keyword.get(opts, :options, []) 109 names = Keyword.get(opts, :names, []) 110 extensions = Keyword.get(opts, :extensions, []) 111 # Associate the lexer with the names 112 for name <- names, do: register_lexer_with_name(name, {lexer, options}) 113 # Associate the lexer with the extensions 114 for extension <- extensions, do: register_lexer_with_extension(extension, {lexer, options}) 115 end 116 117 @doc """ 118 Fetches the lexer from Makeup's registry with the given `name`. 119 120 Returns either `{:ok, {lexer, options}}` or `:error`. 121 This behaviour is based on `Map.fetch/2`. 122 """ 123 def fetch_lexer_by_name(name) do 124 Map.fetch(get_name_registry(), name) 125 end 126 127 @doc """ 128 Fetches the lexer from Makeup's registry with the given `name`. 129 130 Returns either `{lexer, options}` or raises a `KeyError`. 131 This behaviour is based on `Map.fetch!/2`. 132 """ 133 def fetch_lexer_by_name!(name) do 134 Map.fetch!(get_name_registry(), name) 135 end 136 137 @doc """ 138 Gets the lexer from Makeup's registry with the given `name`. 139 140 Returns either `{lexer, options}` or the `default` value 141 (which by default is `nil`). 142 This behaviour is based on `Map.get/3`. 143 """ 144 def get_lexer_by_name(name, default \\ nil) do 145 Map.get(get_name_registry(), name, default) 146 end 147 148 @doc """ 149 Fetches a lexer from Makeup's registry with the given file `extension`. 150 151 Returns either `{:ok, {lexer, options}}` or `:error`. 152 This behaviour is based on `Map.fetch/2`. 153 """ 154 def fetch_lexer_by_extension(name) do 155 Map.fetch(get_extension_registry(), name) 156 end 157 158 @doc """ 159 Fetches the lexer from Makeup's registry with the given file `extension`. 160 161 Returns either `{:ok, {lexer, options}}` or raises a `KeyError`. 162 This behaviour is based on `Map.fetch/2`. 163 """ 164 def fetch_lexer_by_extension!(name) do 165 Map.fetch!(get_extension_registry(), name) 166 end 167 168 @doc """ 169 Gets the lexer from Makeup's registry with the given file `extension`. 170 171 Returns either `{lexer, options}` or the `default` value 172 (which by default is `nil`). 173 This behaviour is based on `Map.get/3`. 174 """ 175 def get_lexer_by_extension(name, default \\ nil) do 176 Map.get(get_extension_registry(), name, default) 177 end 178 179 # --------------------------------------------------------------------------- 180 # Functions not meant to be used outside Makeup 181 # --------------------------------------------------------------------------- 182 # This functions are meant to be run on application startup 183 # or to be used as helpers in Makeup's internal tests. 184 # They are not meant to be invoked by users of Makeup 185 186 @doc false 187 def create_name_registry() do 188 Application.put_env(:makeup, @name_registry_key, %{}) 189 end 190 191 @doc false 192 def create_extension_registry() do 193 Application.put_env(:makeup, @extension_registry_key, %{}) 194 end 195 196 # The `clean_*_registry` are actually the same as the `create_*_registry`, 197 # but that's because of implementation details, so it makes sense to have 198 # separate groups of functions 199 200 @doc false 201 def clean_name_registry() do 202 put_name_registry(%{}) 203 end 204 205 @doc false 206 def clean_extension_registry() do 207 put_extension_registry(%{}) 208 end 209 210 # ---------------------------------------------------------------------------- 211 # Private helper functions 212 # ---------------------------------------------------------------------------- 213 214 defp get_name_registry() do 215 Application.get_env(:makeup, @name_registry_key) 216 end 217 218 defp put_name_registry(registry) do 219 Application.put_env(:makeup, @name_registry_key, registry) 220 end 221 222 defp get_extension_registry() do 223 Application.get_env(:makeup, @extension_registry_key) 224 end 225 226 defp put_extension_registry(registry) do 227 Application.put_env(:makeup, @extension_registry_key, registry) 228 end 229 end